From c4ad3f2f2671388fc2b96e9f53a40bad6a0b5fbe Mon Sep 17 00:00:00 2001 From: Julia Volkova Date: Fri, 31 Oct 2025 11:46:28 +0200 Subject: [PATCH] QUnit tests: divined editors tests to reduce tests per file (#31522) --- .../calendar.options.tests.js | 2583 ++++++++++++ .../calendar.tests.js | 2525 +---------- .../calendarViews.tests.js | 169 +- .../dateRangeBox.options.tests.js | 667 +++ .../dateRangeBox.tests.js | 861 +--- .../datebox.integration.tests.js | 3116 ++++++++++++++ .../datebox.tests.js | 3698 ++--------------- .../dropDownOptions.part1.tests.js | 15 + .../dropDownOptions.part2.tests.js | 15 + .../dropDownOptions.part3.tests.js | 15 + .../dropDownOptions.part4.tests.js | 15 + .../dropDownOptions.part5.tests.js | 19 + .../dropDownOptions.tests.js | 19 +- 13 files changed, 6875 insertions(+), 6842 deletions(-) create mode 100644 packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/calendar.options.tests.js create mode 100644 packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dateRangeBox.options.tests.js create mode 100644 packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/datebox.integration.tests.js create mode 100644 packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownOptions.part1.tests.js create mode 100644 packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownOptions.part2.tests.js create mode 100644 packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownOptions.part3.tests.js create mode 100644 packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownOptions.part4.tests.js create mode 100644 packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownOptions.part5.tests.js rename packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/{ => dropDownParts}/dropDownOptions.tests.js (97%) diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/calendar.options.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/calendar.options.tests.js new file mode 100644 index 000000000000..110c7aa457ee --- /dev/null +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/calendar.options.tests.js @@ -0,0 +1,2583 @@ +import $ from 'jquery'; +import dateUtils from 'core/utils/date'; +import { noop } from 'core/utils/common'; +import fx from 'common/core/animation/fx'; +import Calendar from 'ui/calendar'; +import pointerMock from '../../helpers/pointerMock.js'; +import keyboardMock from '../../helpers/keyboardMock.js'; +import dataUtils from 'core/element_data'; +import dateLocalization from 'common/core/localization/date'; +import localization from 'localization'; + +import 'fluent_blue_light.css!'; + +// calendar +const CALENDAR_CELL_CLASS = 'dx-calendar-cell'; +const CALENDAR_WEEK_NUMBER_CELL_CLASS = 'dx-calendar-week-number-cell'; +const CALENDAR_NAVIGATOR_PREVIOUS_VIEW_CLASS = 'dx-calendar-navigator-previous-view'; +const CALENDAR_NAVIGATOR_NEXT_VIEW_CLASS = 'dx-calendar-navigator-next-view'; +const CALENDAR_TODAY_BUTTON_CLASS = 'dx-calendar-today-button'; +const CALENDAR_CAPTION_BUTTON_CLASS = 'dx-calendar-caption-button'; +const CALENDAR_OTHER_VIEW_CLASS = 'dx-calendar-other-view'; +const CALENDAR_VIEWS_WRAPPER_CLASS = 'dx-calendar-views-wrapper'; + +// calendar view +const CALENDAR_SELECTED_DATE_CLASS = 'dx-calendar-selected-date'; +const CALENDAR_CELL_IN_RANGE_CLASS = 'dx-calendar-cell-in-range'; +const CALENDAR_CELL_RANGE_HOVER_CLASS = 'dx-calendar-cell-range-hover'; +const CALENDAR_CELL_RANGE_HOVER_START_CLASS = 'dx-calendar-cell-range-hover-start'; +const CALENDAR_CELL_RANGE_HOVER_END_CLASS = 'dx-calendar-cell-range-hover-end'; +const CALENDAR_RANGE_START_DATE_CLASS = 'dx-calendar-range-start-date'; +const CALENDAR_RANGE_END_DATE_CLASS = 'dx-calendar-range-end-date'; + +const CALENDAR_DATE_VALUE_KEY = 'dxDateValueKey'; + +const VIEW_ANIMATION_DURATION = 350; + +const ENTER_KEY_CODE = 'Enter'; +const PAGE_UP_KEY_CODE = 'PageUp'; +const PAGE_DOWN_KEY_CODE = 'PageDown'; +const END_KEY_CODE = 'End'; +const HOME_KEY_CODE = 'Home'; +const LEFT_ARROW_KEY_CODE = 'ArrowLeft'; +const UP_ARROW_KEY_CODE = 'ArrowUp'; +const RIGHT_ARROW_KEY_CODE = 'ArrowRight'; +const DOWN_ARROW_KEY_CODE = 'ArrowDown'; + +const getBeforeViewInstance = (calendar) => { + return calendar._beforeView; +}; +const getCurrentViewInstance = (calendar) => { + return calendar._view; +}; +const getAdditionalViewInstance = (calendar) => { + return calendar._additionalView; +}; +const getAfterViewInstance = (calendar) => { + return calendar._afterView; +}; + +function triggerKeydown($element, key, additionOptions) { + const options = { key: key }; + + $.extend(options, additionOptions); + + const e = $.Event('keydown', options); + $element.trigger(e); +} + +QUnit.module('Options', { + beforeEach: function() { + fx.off = true; + + this.$element = $('
').appendTo('#qunit-fixture'); + this.calendar = this.$element.dxCalendar().dxCalendar('instance'); + + this.reinit = (options) => { + this.$element.remove(); + this.$element = $('
').appendTo('#qunit-fixture'); + this.calendar = this.$element.dxCalendar(options).dxCalendar('instance'); + }; + }, + afterEach: function() { + this.$element.remove(); + fx.off = false; + } +}, () => { + QUnit.test('changing the \'value\' option must invoke the \'onValueChanged\' action', function(assert) { + this.reinit({ + onValueChanged: () => { + assert.ok(true); + } + }); + this.calendar.option('value', new Date(2002, 2, 2)); + }); + + QUnit.test('firstDayOfWeek option', function(assert) { + const getFirstWeekDayCell = () => { + return getCurrentViewInstance(this.calendar).$element().find('th').get(0); + }; + + let $firstWeekDayCell = getFirstWeekDayCell(); + assert.strictEqual($firstWeekDayCell.abbr, 'Sunday', 'first day of week is correct'); + + this.calendar.option('firstDayOfWeek', 1); + + $firstWeekDayCell = getFirstWeekDayCell(); + assert.strictEqual($firstWeekDayCell.abbr, 'Monday', 'first day of week is correct after runtime option change'); + + this.calendar.option('firstDayOfWeek', 2); + + $firstWeekDayCell = getFirstWeekDayCell(); + assert.strictEqual($firstWeekDayCell.abbr, 'Tuesday', 'first day of week is correct after runtime option change'); + }); + + [ + { localeID: 'de', expectedFirstDayOfWeek: 'Montag' }, + { localeID: 'en', expectedFirstDayOfWeek: 'Sunday' }, + { localeID: 'ja', expectedFirstDayOfWeek: '日曜日' }, + // eslint-disable-next-line i18n/no-russian-character + { localeID: 'ru', expectedFirstDayOfWeek: 'понедельник' }, + { localeID: 'zh', expectedFirstDayOfWeek: '星期日' }, + { localeID: 'hr', expectedFirstDayOfWeek: 'ponedjeljak' }, + { localeID: 'ar', expectedFirstDayOfWeek: 'السبت' }, + { localeID: 'el', expectedFirstDayOfWeek: 'Δευτέρα' }, + { localeID: 'ca', expectedFirstDayOfWeek: 'dilluns' }, + ].forEach(({ localeID, expectedFirstDayOfWeek }) => { + QUnit.test(`firstDayOfWeek should depend from locale: ${localeID}`, function(assert) { + const getFirstWeekDayCell = () => { + return getCurrentViewInstance(this.calendar).$element().find('th').get(0); + }; + + const currentLocale = localization.locale(); + + try { + localization.locale(localeID); + + this.reinit({}); + + const $firstWeekDayCell = getFirstWeekDayCell(); + assert.strictEqual($firstWeekDayCell.abbr, expectedFirstDayOfWeek, 'first day of week is correct'); + } finally { + localization.locale(currentLocale); + } + }); + }); + + [ + { weekNumberRule: 'auto', firstDayOfWeek: 1, expectedCalls: { firstFourDays: 36, firstDay: 0, fullWeek: 0 } }, + { weekNumberRule: 'auto', firstDayOfWeek: 0, expectedCalls: { firstFourDays: 0, firstDay: 36, fullWeek: 0 } }, + { weekNumberRule: 'auto', firstDayOfWeek: 5, expectedCalls: { firstFourDays: 0, firstDay: 36, fullWeek: 0 } }, + { weekNumberRule: 'firstDay', firstDayOfWeek: 1, expectedCalls: { firstFourDays: 0, firstDay: 36, fullWeek: 0 } }, + { weekNumberRule: 'firstDay', firstDayOfWeek: 0, expectedCalls: { firstFourDays: 0, firstDay: 36, fullWeek: 0 } }, + { weekNumberRule: 'firstDay', firstDayOfWeek: 5, expectedCalls: { firstFourDays: 0, firstDay: 36, fullWeek: 0 } }, + { weekNumberRule: 'firstFourDays', firstDayOfWeek: 1, expectedCalls: { firstFourDays: 36, firstDay: 0, fullWeek: 0 } }, + { weekNumberRule: 'firstFourDays', firstDayOfWeek: 0, expectedCalls: { firstFourDays: 36, firstDay: 0, fullWeek: 0 } }, + { weekNumberRule: 'firstFourDays', firstDayOfWeek: 5, expectedCalls: { firstFourDays: 36, firstDay: 0, fullWeek: 0 } }, + { weekNumberRule: 'fullWeek', firstDayOfWeek: 1, expectedCalls: { firstFourDays: 0, firstDay: 0, fullWeek: 36 } }, + { weekNumberRule: 'fullWeek', firstDayOfWeek: 0, expectedCalls: { firstFourDays: 0, firstDay: 0, fullWeek: 36 } }, + { weekNumberRule: 'fullWeek', firstDayOfWeek: 5, expectedCalls: { firstFourDays: 0, firstDay: 0, fullWeek: 36 } }, + ].forEach(({ weekNumberRule, firstDayOfWeek, expectedCalls }) => { + QUnit.test(`weekNumberRule option: weekNumberRule="${weekNumberRule}", firstDayOfWeek="${firstDayOfWeek}"`, function(assert) { + const dateUtilsCallCountMap = { + firstDay: 0, + firstFourDays: 0, + fullWeek: 0 + }; + const getWeekNumberStub = sinon.stub(dateUtils, 'getWeekNumber').callsFake((date, firstDayOfWeek, rule) => { + dateUtilsCallCountMap[rule]++; + }); + + try { + this.calendar.option({ + firstDayOfWeek, + weekNumberRule, + showWeekNumbers: true, + currentDate: new Date(2020, 0, 1), + }); + + ['firstDay', 'firstFourDays', 'fullWeek'].forEach((rule) => { + assert.strictEqual(dateUtilsCallCountMap[rule], expectedCalls[rule], `getWeekNumber called ${expectedCalls[rule]} times for ${rule} rule`); + }); + } finally { + getWeekNumberStub.restore(); + } + }); + }); + + QUnit.test('dateSerializationFormat option', function(assert) { + this.calendar.option({ + dateSerializationFormat: 'yyyy-MM-dd', + currentDate: new Date(2020, 0, 0) + }); + + const $cell = this.$element.find(`.${CALENDAR_CELL_CLASS}`).eq(4); + $($cell).trigger('dxclick'); + + const selectedFormattedValue = '2019-11-28'; + const value = this.calendar.option('value'); + assert.strictEqual(value, selectedFormattedValue, 'value format is correct after dateSerializationFormat option runtime change'); + }); + + QUnit.test('cellTemplate option', function(assert) { + this.calendar.option({ + cellTemplate: function() { + return 'Custom template'; + }, + currentDate: new Date(2020, 0, 0) + }); + + const $cell = this.$element.find(`.${CALENDAR_CELL_CLASS}`).eq(4); + const cellContent = $cell.text(); + + assert.strictEqual(cellContent, 'Custom template', 'cell content is correct after cellTemplate runtime change'); + }); + + QUnit.test('cellTemplate is rendered fow week cell', function(assert) { + this.calendar.option({ + cellTemplate: function(cellData, cellIndex) { + return cellIndex === -1 ? 'Week cell template' : `${cellData.text}`; + }, + value: new Date(2022, 0, 1), + showWeekNumbers: true + }); + + const $cell = this.$element.find(`.${CALENDAR_WEEK_NUMBER_CELL_CLASS}`).eq(0); + const cellContent = $cell.text(); + + assert.strictEqual(cellContent, 'Week cell template'); + }); + + QUnit.test('showTodayButton option', function(assert) { + const getTodayButton = () => this.$element.find(`.${CALENDAR_TODAY_BUTTON_CLASS}`).get(0); + + this.calendar.option('showTodayButton', true); + + let $todayButton = getTodayButton(); + assert.strictEqual($($todayButton).text(), 'Today', 'todayButton is rendered after showTodayButton runtime change to true'); + + this.calendar.option('showTodayButton', false); + $todayButton = getTodayButton(); + assert.strictEqual($todayButton, undefined, 'todayButton is not rendered after showTodayButton runtime change to false'); + }); + + QUnit.test('todayButtonText option initialize', function(assert) { + const getTodayButton = () => this.$element.find(`.${CALENDAR_TODAY_BUTTON_CLASS}`).get(0); + + this.reinit({ + showTodayButton: true, + todayButtonText: 'Custom text', + }); + + const $todayButton = getTodayButton(); + assert.strictEqual($($todayButton).text(), 'Custom text', 'todayButton text matches the todayButtonText option'); + }); + + QUnit.test('todayButtonText option', function(assert) { + const getTodayButton = () => this.$element.find(`.${CALENDAR_TODAY_BUTTON_CLASS}`).get(0); + + this.calendar.option({ + showTodayButton: true, + todayButtonText: 'Custom text', + }); + + const $todayButton = getTodayButton(); + assert.strictEqual($($todayButton).text(), 'Custom text', 'todayButton text matches the todayButtonText option'); + }); + + QUnit.test('onCellClick option runtime change', function(assert) { + const getCellElement = () => this.$element.find(`.${CALENDAR_CELL_CLASS}`).eq(4); + + const firstClickHandler = sinon.spy(); + const secondClickHandler = sinon.spy(); + + this.calendar.option({ + currentDate: new Date(2010, 10, 10), + focusStateEnabled: true, + onCellClick: firstClickHandler + }); + + $(getCellElement()).trigger('dxclick'); + assert.ok(firstClickHandler.calledOnce, 'firstClickHandler is called once'); + + this.calendar.option('onCellClick', secondClickHandler); + + $(getCellElement()).trigger('dxclick'); + assert.ok(secondClickHandler.calledOnce, 'secondClickHandler is called once after onCellClick runtime option change'); + }); + + QUnit.test('onCellClick option - subscription by "on" method', function(assert) { + const getCellElement = () => this.$element.find(`.${CALENDAR_CELL_CLASS}`).eq(4); + + const clickHandler = sinon.spy(); + + this.calendar.option({ + currentDate: new Date(2010, 10, 10), + focusStateEnabled: true + }); + this.calendar.on('cellClick', clickHandler); + + $(getCellElement()).trigger('dxclick'); + assert.ok(clickHandler.calledOnce, 'cellClick is called'); + + this.calendar.off('cellClick', clickHandler); + + $(getCellElement()).trigger('dxclick'); + assert.ok(clickHandler.calledOnce, 'cellClick is not called second time'); + }); + + QUnit.test('onContouredChanged option runtime change', function(assert) { + const firstHandler = sinon.spy(); + const secondHandler = sinon.spy(); + + this.reinit({ + value: null, + onContouredChanged: firstHandler, + focusStateEnabled: true + }); + + assert.ok(firstHandler.calledOnce, 'first handler has been called'); + + this.calendar.option('onContouredChanged', secondHandler); + this.calendar.focus(); + triggerKeydown($(this.calendar._$viewsWrapper), UP_ARROW_KEY_CODE, { ctrlKey: true }); + + assert.ok(secondHandler.calledOnce, 'second handler has been called'); + }); + + QUnit.test('onContouredChanged option - subscription by "on" method', function(assert) { + const goNextView = () => { + $(this.$element.find(`.${CALENDAR_NAVIGATOR_NEXT_VIEW_CLASS}`)).trigger('dxclick'); + }; + + const handler = sinon.spy(); + this.reinit({ + value: null, + focusStateEnabled: true + }); + + this.calendar.on('contouredChanged', handler); + goNextView(); + assert.ok(handler.calledOnce, 'handler is called'); + + this.calendar.off('contouredChanged', handler); + goNextView(); + assert.ok(handler.calledOnce, 'handler is not called second time'); + }); + + QUnit.test('onCellClick return not \'undefined\' after click on cell', function(assert) { + const clickHandler = sinon.spy(noop); + + this.reinit({ + currentDate: new Date(2010, 10, 10), + focusStateEnabled: true, + onCellClick: clickHandler + }); + + const $cell = this.$element.find(`.${CALENDAR_CELL_CLASS}`).eq(4); + $($cell).trigger('dxclick'); + + assert.ok(clickHandler.calledOnce, 'onCellClick called once'); + + const params = clickHandler.getCall(0).args[0]; + assert.ok(params, 'Event params should be passed'); + assert.ok(params.event, 'Event should be passed'); + assert.ok(params.component, 'Component should be passed'); + assert.ok(params.element, 'Element should be passed'); + }); + + QUnit.test('onCellClick should not be fired when zoomLevel change required (for datebox integration)', function(assert) { + const clickSpy = sinon.spy(); + + this.reinit({ + onCellClick: clickSpy, + zoomLevel: 'year', + maxZoomLevel: 'month' + }); + + const $cell = $(getCurrentViewInstance(this.calendar).$element().find('.' + CALENDAR_CELL_CLASS).eq(3)); + $($cell).trigger('dxclick'); + + assert.equal(clickSpy.callCount, 0, 'onCellClick was not fired'); + }); + + QUnit.test('Calendar should not allow to select date in disabled state changed in runtime (T196663)', function(assert) { + this.reinit({ + value: new Date(2013, 11, 15), + currentDate: new Date(2013, 11, 15) + }); + + this.calendar.option('disabled', true); + $(this.$element.find('[data-value=\'2013/12/11\']')).trigger('dxclick'); + assert.deepEqual(this.calendar.option('value'), new Date(2013, 11, 15)); + }); + + QUnit.test('When initialized without currentDate, calendar must try to infer it from value', function(assert) { + const date = new Date(2014, 11, 11); + + this.reinit({ + value: new Date(date) + }); + + assert.deepEqual(this.calendar.option('currentDate'), date); + }); + + QUnit.test('calendar view should be changed on the \'currentDate\' option change', function(assert) { + const calendar = this.calendar; + const oldDate = getCurrentViewInstance(calendar).option('date'); + + calendar.option('currentDate', new Date(2013, 11, 15)); + assert.notDeepEqual(getCurrentViewInstance(calendar).option('date'), oldDate, 'view is changed'); + }); + + QUnit.test('contoured date displaying should depend on \'skipFocusCheck\' option', function(assert) { + this.reinit({ + value: new Date(2015, 10, 18), + skipFocusCheck: true + }); + + assert.deepEqual(getCurrentViewInstance(this.calendar).option('contouredDate'), new Date(2015, 10, 18), 'view contoured is set'); + }); + + QUnit.test('_todayDate option should be passed to calendar view', function(assert) { + const calendarTodayDate = () => new Date(2021, 1, 1); + + this.reinit({ _todayDate: calendarTodayDate }); + assert.strictEqual(getCurrentViewInstance(this.calendar).option('_todayDate'), calendarTodayDate, '_todayDate is passed to calendar view'); + }); + + QUnit.test('_todayDate option should be passed to calendar view after runtime option change', function(assert) { + const calendarTodayDate = () => new Date(2021, 1, 1); + + this.calendar.option({ _todayDate: calendarTodayDate }); + assert.strictEqual(getCurrentViewInstance(this.calendar).option('_todayDate'), calendarTodayDate, '_todayDate is passed to calendar view'); + }); + + QUnit.test('_todayDate should return new Date() if it is not specified', function(assert) { + const today = new Date(); + const result = this.calendar.option('_todayDate')(); + + today.setHours(0, 0, 0, 0); + result.setHours(0, 0, 0, 0); + + assert.deepEqual(today, result, 'today date is correct'); + }); + + QUnit.module('SelectionMode', { + beforeEach: function() { + this.options = { + value: [new Date('01/15/2023'), new Date('02/01/2023'), new Date('02/05/2023')], + }; + } + }, () => { + ['multiple', 'range'].forEach((selectionMode) => { + QUnit.test(`Date from value option is not selected when selectionMode is ${selectionMode}`, function(assert) { + this.reinit({ + ...this.options, + selectionMode + }); + const $cell = this.$element.find('*[data-value="2023/01/07"]'); + + assert.notOk($cell.hasClass(CALENDAR_SELECTED_DATE_CLASS)); + }); + + [ + { + value: [new Date('01/05/2023'), new Date('02/01/2023')], + type: 'dates' + }, + { + value: ['01/05/2023', '02/01/2023'], + type: 'strings' + }, + { + value: [1672916400000, 1675249200000], + type: 'numbers' + } + ].forEach(({ value, type }) => { + QUnit.test(`Two dates are selected when selectionMode = ${selectionMode} and value are defined as ${type}`, function(assert) { + this.reinit({ + selectionMode, + value + }); + const $cells = $(getCurrentViewInstance(this.calendar).$element().find(`.${CALENDAR_SELECTED_DATE_CLASS}`)); + + assert.strictEqual($($cells[0]).data('value'), '2023/01/05'); + assert.strictEqual($($cells[1]).data('value'), '2023/02/01'); + }); + }); + }); + + QUnit.module('CurrentDate', {}, () => { + ['multiple', 'range'].forEach((selectionMode) => { + QUnit.test(`Should be equal to the lowest defined date in value on init (selectionMode=${selectionMode}`, function(assert) { + this.reinit({ + value: [null, new Date('01/15/2023'), new Date('02/01/2023')], + selectionMode + }); + const { currentDate, value } = this.calendar.option(); + + assert.deepEqual(currentDate, new Date(Math.min(...value.filter(value => value)))); + }); + + QUnit.test(`Should be equal to the lowest date in value on runtime value change (selectionMode=${selectionMode}`, function(assert) { + this.reinit({ selectionMode }); + this.calendar.option('value', [new Date(), new Date('2020/02/02')]); + const { currentDate, value } = this.calendar.option(); + + assert.deepEqual(currentDate, value[1]); + }); + + QUnit.test(`Should be equal to new selected cell date when selectionMode = ${selectionMode}`, function(assert) { + this.reinit({ + ...this.options, + selectionMode + }); + const $cell = this.$element.find('*[data-value="2023/01/16"]'); + + $cell.trigger('dxclick'); + + const currentDate = this.calendar.option('currentDate'); + + assert.deepEqual(currentDate, new Date('2023/01/16')); + }); + }); + + QUnit.test('Should be equal to deselected cell date when selectionMode = multiple', function(assert) { + this.reinit({ + ...this.options, + selectionMode: 'multiple' + }); + const $cell = this.$element.find('*[data-value="2023/01/15"]'); + + $cell.trigger('dxclick'); + + const currentDate = this.calendar.option('currentDate'); + + assert.deepEqual(currentDate, new Date('2023/01/15')); + }); + }); + + QUnit.module('Multiple', { + beforeEach: function() { + this.reinit({ + ...this.options, + selectionMode: 'multiple' + }); + } + }, () => { + QUnit.test('It should be possible to select another value by click', function(assert) { + const $cell = this.$element.find('*[data-value="2023/01/16"]'); + + $cell.trigger('dxclick'); + + assert.strictEqual(this.calendar.option('value').length, 4); + assert.ok($cell.hasClass(CALENDAR_SELECTED_DATE_CLASS)); + }); + + QUnit.test('It should be possible to deselect already selected value by click', function(assert) { + const $cell = $(getCurrentViewInstance(this.calendar).$element().find('*[data-value="2023/01/15"]')); + + $cell.trigger('dxclick'); + + assert.strictEqual(this.calendar.option('value').length, 2); + assert.notOk($cell.hasClass(CALENDAR_SELECTED_DATE_CLASS)); + }); + }); + + QUnit.module('Range', { + beforeEach: function() { + this.reinit({ + value: ['2023/01/13', '2023/01/17', '2023/01/20'], + selectionMode: 'range' + }); + } + }, () => { + QUnit.test('Only first two dates from value option should be selected', function(assert) { + const $cell1 = this.$element.find('*[data-value="2023/01/13"]'); + const $cell2 = this.$element.find('*[data-value="2023/01/17"]'); + const $cell3 = this.$element.find('*[data-value="2023/01/20"]'); + + assert.ok($cell1.hasClass(CALENDAR_SELECTED_DATE_CLASS)); + assert.ok($cell2.hasClass(CALENDAR_SELECTED_DATE_CLASS)); + assert.notOk($cell3.hasClass(CALENDAR_SELECTED_DATE_CLASS)); + }); + + QUnit.test(`Start value cell should have ${CALENDAR_RANGE_START_DATE_CLASS} class`, function(assert) { + const $cell = $(getCurrentViewInstance(this.calendar).$element().find('*[data-value="2023/01/13"]')); + + assert.ok($cell.hasClass(CALENDAR_RANGE_START_DATE_CLASS)); + }); + + QUnit.test(`End value cell should have ${CALENDAR_RANGE_END_DATE_CLASS} class`, function(assert) { + const $cell = $(getCurrentViewInstance(this.calendar).$element().find('*[data-value="2023/01/17"]')); + + assert.ok($cell.hasClass(CALENDAR_RANGE_END_DATE_CLASS)); + }); + + QUnit.test(`Cells between startDate and endDate should have ${CALENDAR_CELL_IN_RANGE_CLASS} class`, function(assert) { + const $cell = $(getCurrentViewInstance(this.calendar).$element().find('*[data-value="2023/01/15"]')); + + assert.ok($cell.hasClass(CALENDAR_CELL_IN_RANGE_CLASS)); + }); + + + QUnit.test(`Cells between startDate and endDate should have ${CALENDAR_CELL_IN_RANGE_CLASS} class even after currentDate runtime change (T1253076)`, function(assert) { + this.reinit({ + value: ['2025/01/01', '2025/12/31'], + selectionMode: 'range', + viewsCount: 2, + }); + + this.calendar.option('currentDate', new Date('2025-12-31')); + + const $prevButton = $(this.$element.find(`.${CALENDAR_NAVIGATOR_PREVIOUS_VIEW_CLASS}`)); + $prevButton.trigger('dxclick'); + + const $cell = $(getCurrentViewInstance(this.calendar).$element().find('*[data-value="2025/11/01"]')); + + assert.ok($cell.hasClass(CALENDAR_CELL_IN_RANGE_CLASS), 'cell is highlighted'); + }); + + QUnit.test('Should reselect startDate and clear endDate on click when both value are defined', function(assert) { + const expectedValue = [new Date('2023/01/11'), null]; + const $cell = $(getCurrentViewInstance(this.calendar).$element().find('*[data-value="2023/01/11"]')); + + $cell.trigger('dxclick'); + + assert.deepEqual(this.calendar.option('value'), expectedValue); + }); + + QUnit.test('Should select endDate on cell click when startDate is alredy defined and endDate not', function(assert) { + this.reinit({ + value: ['2023/01/13', null], + selectionMode: 'range' + }); + const expectedValue = [new Date('2023/01/13'), new Date('2023/01/15')]; + const $cell = $(getCurrentViewInstance(this.calendar).$element().find('*[data-value="2023/01/15"]')); + + $cell.trigger('dxclick'); + + assert.deepEqual(this.calendar.option('value'), expectedValue); + }); + + QUnit.test('Should swap startDate and endDate on cell when clicked endDate is less then startDate', function(assert) { + this.reinit({ + value: ['2023/01/13', null], + selectionMode: 'range' + }); + const expectedValue = [new Date('2023/01/07'), new Date('2023/01/13')]; + const $cell = $(getCurrentViewInstance(this.calendar).$element().find('*[data-value="2023/01/07"]')); + + $cell.trigger('dxclick'); + + assert.deepEqual(this.calendar.option('value'), expectedValue); + }); + + [ + { + value: [null, null], + scenario: 'when both values are not defined' + }, + { + value: ['2023/01/13', '2023/01/17'], + scenario: 'when both values are defined' + } + ].forEach(({ value, scenario }) => { + QUnit.test(`Cells should not have ${CALENDAR_CELL_IN_RANGE_CLASS} class on hover ${scenario}`, function(assert) { + this.reinit({ + value, + selectionMode: 'range' + }); + const $cell = $(getCurrentViewInstance(this.calendar).$element().find('*[data-value="2023/01/25"]')); + + $cell.trigger('mouseenter'); + + assert.notOk($cell.hasClass(CALENDAR_CELL_IN_RANGE_CLASS)); + }); + }); + + QUnit.test(`Cells should have ${CALENDAR_CELL_RANGE_HOVER_CLASS} class on hover when only startDate is defined`, function(assert) { + this.reinit({ + value: ['2023/01/13', null], + selectionMode: 'range' + }); + + const getCell = (date) => { + return $(getCurrentViewInstance(this.calendar).$element().find(`*[data-value="${date}"]`)); + }; + + getCell('2023/01/15').trigger('mouseenter'); + + const hoveredRange = getCurrentViewInstance(this.calendar).option('hoveredRange'); + + assert.strictEqual(hoveredRange.length, 3, 'hovered range is correct'); + + assert.strictEqual(getCell('2023/01/15').hasClass(CALENDAR_CELL_RANGE_HOVER_CLASS), true, `${CALENDAR_CELL_RANGE_HOVER_CLASS} class`); + assert.strictEqual(getCell('2023/01/15').hasClass(CALENDAR_CELL_RANGE_HOVER_END_CLASS), true, `${CALENDAR_CELL_RANGE_HOVER_END_CLASS} class`); + assert.strictEqual(getCell('2023/01/15').hasClass(CALENDAR_CELL_RANGE_HOVER_START_CLASS), false, `${CALENDAR_CELL_RANGE_HOVER_START_CLASS} class`); + + assert.strictEqual(getCell('2023/01/14').hasClass(CALENDAR_CELL_RANGE_HOVER_CLASS), true, `${CALENDAR_CELL_RANGE_HOVER_CLASS} class`); + assert.strictEqual(getCell('2023/01/14').hasClass(CALENDAR_CELL_RANGE_HOVER_END_CLASS), false, `${CALENDAR_CELL_RANGE_HOVER_END_CLASS} class`); + assert.strictEqual(getCell('2023/01/14').hasClass(CALENDAR_CELL_RANGE_HOVER_START_CLASS), false, `${CALENDAR_CELL_RANGE_HOVER_START_CLASS} class`); + + assert.strictEqual(getCell('2023/01/13').hasClass(CALENDAR_CELL_RANGE_HOVER_CLASS), true, `${CALENDAR_CELL_RANGE_HOVER_CLASS} class`); + assert.strictEqual(getCell('2023/01/13').hasClass(CALENDAR_CELL_RANGE_HOVER_END_CLASS), false, `${CALENDAR_CELL_RANGE_HOVER_END_CLASS} class`); + assert.strictEqual(getCell('2023/01/13').hasClass(CALENDAR_CELL_RANGE_HOVER_START_CLASS), true, `${CALENDAR_CELL_RANGE_HOVER_START_CLASS} class`); + }); + + QUnit.test('Hovered range should be cleared after mouseleave on viewsWrapper element', function(assert) { + this.reinit({ + value: ['2023/01/13', null], + selectionMode: 'range' + }); + const $cell = $(getCurrentViewInstance(this.calendar).$element().find('*[data-value="2023/01/15"]')); + + $cell.trigger('mouseenter'); + + assert.strictEqual(getCurrentViewInstance(this.calendar).option('hoveredRange').length, 3, 'hovered range is correct'); + + const $viewsWrapper = $(this.$element.find(`.${CALENDAR_VIEWS_WRAPPER_CLASS}`)); + + $viewsWrapper.trigger('mouseleave'); + + assert.strictEqual(getCurrentViewInstance(this.calendar).option('hoveredRange').length, 0, 'hovered range is cleared'); + }); + + QUnit.test('Selected range should be reduced when difference between startDate and endDate is bigger than four mounths', function(assert) { + this.reinit({ + value: ['1996/01/05', '2121/03/07'], + selectionMode: 'range', + }); + + const selectedRange = getCurrentViewInstance(this.calendar).option('range'); + + assert.ok(selectedRange.length < 240); + }); + + [1, 2].forEach((viewsCount) => { + QUnit.test(`Big range should start from first date of before view and end on last date of after view (viewsCount=${viewsCount})`, function(assert) { + this.reinit({ + value: ['1996/01/05', '2345/03/07'], + selectionMode: 'range', + viewsCount, + }); + + this.calendar.option('currentDate', new Date('2023/07/24')); + + const expectedRangeStart = new Date('2023/06/01'); + const expectedRangeEnd = viewsCount === 1 ? new Date('2023/08/31') : new Date('2023/09/30'); + const selectedRange = getCurrentViewInstance(this.calendar).option('range'); + const rangeStart = selectedRange[0]; + const rangeEnd = selectedRange[selectedRange.length - 1]; + + assert.deepEqual(rangeStart, expectedRangeStart, 'range start date is first date in views'); + assert.deepEqual(rangeEnd, expectedRangeEnd, 'range end date is last date in views'); + }); + + QUnit.test(`Big range should start from start date if start date is date in before view (viewsCount=${viewsCount})`, function(assert) { + this.reinit({ + value: ['1996/01/05', '2345/03/07'], + selectionMode: 'range', + viewsCount, + }); + + this.calendar.option('currentDate', new Date('2023/07/24')); + this.calendar.option('currentDate', new Date('1996/02/15')); + + const expectedRangeStart = new Date('1996/01/05'); + const expectedRangeEnd = viewsCount === 1 ? new Date('1996/03/31') : new Date('1996/04/30'); + const selectedRange = getCurrentViewInstance(this.calendar).option('range'); + const rangeStart = selectedRange[0]; + const rangeEnd = selectedRange[selectedRange.length - 1]; + + assert.deepEqual(rangeStart, expectedRangeStart, 'range start date is start date'); + assert.deepEqual(rangeEnd, expectedRangeEnd, 'range end date is last date in views'); + }); + + QUnit.test(`Big range should end on end date if end date is date from views (viewsCount=${viewsCount})`, function(assert) { + this.reinit({ + value: ['1996/01/05', '2345/03/07'], + selectionMode: 'range', + viewsCount, + }); + + this.calendar.option('currentDate', new Date('2345/03/15')); + + const expectedRangeStart = new Date('2345/02/01'); + const expectedRangeEnd = new Date('2345/03/07'); + const selectedRange = getCurrentViewInstance(this.calendar).option('range'); + const rangeStart = selectedRange[0]; + const rangeEnd = selectedRange[selectedRange.length - 1]; + + assert.deepEqual(rangeStart, expectedRangeStart, 'range start date is first date in views'); + assert.deepEqual(rangeEnd, expectedRangeEnd, 'range end date is end date'); + }); + }); + + [ + [null, null], + [new Date(2021, 9, 17), null], + [null, new Date(2021, 10, 25)], + [new Date(2021, 9, 10), new Date(2021, 9, 17)] + ].forEach((value) => { + QUnit.test(`Click by cell should change startDate value if allowChangeSelectionOrder is true and currentSelection is startDate, initial value: ${JSON.stringify(value)}`, function(assert) { + this.reinit({ + value, + selectionMode: 'range', + allowChangeSelectionOrder: true, + currentSelection: 'startDate', + }); + + let $startDateCell = $(this.calendar.$element()).find(`.${CALENDAR_CELL_CLASS}`).eq(20); + let startCellDate = dataUtils.data($startDateCell.get(0), CALENDAR_DATE_VALUE_KEY); + $startDateCell.trigger('dxclick'); + + assert.deepEqual(this.calendar.option('value'), [startCellDate, value[1]]); + + $startDateCell = $(this.calendar.$element()).find(`.${CALENDAR_CELL_CLASS}`).eq(15); + startCellDate = dataUtils.data($startDateCell.get(0), CALENDAR_DATE_VALUE_KEY); + $startDateCell.trigger('dxclick'); + + assert.deepEqual(this.calendar.option('value'), [startCellDate, value[1]]); + }); + + QUnit.test(`Click by cell should change startDate value and reselect endDate if allowChangeSelectionOrder is true and currentSelection is startDate, startDate > endDate, initial value: ${JSON.stringify(value)}`, function(assert) { + this.reinit({ + value, + selectionMode: 'range', + allowChangeSelectionOrder: true, + currentSelection: 'startDate', + }); + + const $startDateCell = $(this.calendar.$element()).find(`.${CALENDAR_CELL_CLASS}`).eq(30); + const startCellDate = dataUtils.data($startDateCell.get(0), CALENDAR_DATE_VALUE_KEY); + $startDateCell.trigger('dxclick'); + + assert.deepEqual(this.calendar.option('value'), [startCellDate, null]); + }); + + QUnit.test(`Click by cell should change endDate value and reselect startDate if allowChangeSelectionOrder is true and currentSelection is endDate, endDate < startDate, initial value: ${JSON.stringify(value)}`, function(assert) { + this.reinit({ + value, + selectionMode: 'range', + allowChangeSelectionOrder: true, + currentSelection: 'endDate', + }); + + const $endCellDate = $(this.calendar.$element()).find(`.${CALENDAR_CELL_CLASS}`).eq(7); + const endCellDate = dataUtils.data($endCellDate.get(0), CALENDAR_DATE_VALUE_KEY); + $endCellDate.trigger('dxclick'); + + assert.deepEqual(this.calendar.option('value'), endCellDate < value[0] ? [endCellDate, null] : [null, endCellDate]); + }); + + QUnit.test(`Click by cell should change endDate value if allowChangeSelectionOrder is true and currentSelection is endDate, initial value: ${JSON.stringify(value)}`, function(assert) { + this.reinit({ + value, + selectionMode: 'range', + allowChangeSelectionOrder: true, + currentSelection: 'endDate', + }); + + let $endDateCell = $(this.calendar.$element()).find(`.${CALENDAR_CELL_CLASS}`).eq(25); + let endCellDate = dataUtils.data($endDateCell.get(0), CALENDAR_DATE_VALUE_KEY); + $endDateCell.trigger('dxclick'); + + assert.deepEqual(this.calendar.option('value'), [value[0], endCellDate]); + + $endDateCell = $(this.calendar.$element()).find(`.${CALENDAR_CELL_CLASS}`).eq(30); + endCellDate = dataUtils.data($endDateCell.get(0), CALENDAR_DATE_VALUE_KEY); + $endDateCell.trigger('dxclick'); + + assert.deepEqual(this.calendar.option('value'), [value[0], endCellDate]); + }); + + QUnit.test(`Click by cell should change endDate then startDate value if allowChangeSelectionOrder is true and currentSelection is endDate then startDate, initial value: ${JSON.stringify(value)}`, function(assert) { + this.reinit({ + value, + selectionMode: 'range', + allowChangeSelectionOrder: true, + currentSelection: 'endDate', + }); + + const $endDateCell = $(this.calendar.$element()).find(`.${CALENDAR_CELL_CLASS}`).eq(30); + const endCellDate = dataUtils.data($endDateCell.get(0), CALENDAR_DATE_VALUE_KEY); + $endDateCell.trigger('dxclick'); + + assert.deepEqual(this.calendar.option('value'), [value[0], endCellDate]); + + this.calendar.option('currentSelection', 'startDate'); + + const $startDateCell = $(this.calendar.$element()).find(`.${CALENDAR_CELL_CLASS}`).eq(10); + const startCellDate = dataUtils.data($startDateCell.get(0), CALENDAR_DATE_VALUE_KEY); + $startDateCell.trigger('dxclick'); + + assert.deepEqual(this.calendar.option('value'), [startCellDate, endCellDate]); + }); + }); + + QUnit.test('Range should not be displayed on cell hover if only startDate is defined and allowChangeSelectionOrder is true and currentSelection is startDate', function(assert) { + this.reinit({ + value: ['2023/04/01', null], + selectionMode: 'range', + allowChangeSelectionOrder: true, + currentSelection: 'startDate', + }); + + const $cellToHover = $(this.calendar.$element()).find(`.${CALENDAR_CELL_CLASS}`).eq(20); + + $cellToHover.trigger('mouseenter'); + + assert.notOk($cellToHover.hasClass(CALENDAR_CELL_IN_RANGE_CLASS)); + }); + }); + + [ + { + initialSelectionMode: 'multiple', + newSelectionMode: 'single', + optionName: 'value', + expectedValue: null, + }, + { + initialSelectionMode: 'single', + newSelectionMode: 'range', + optionName: 'value', + expectedValue: [null, null], + }, + { + initialSelectionMode: 'range', + newSelectionMode: 'multiple', + optionName: 'value', + expectedValue: [], + }, + ].forEach(({ initialSelectionMode, newSelectionMode, optionName, expectedValue }) => { + QUnit.test(`Value should be restored after switching from ${initialSelectionMode} to ${newSelectionMode} selectionMode`, function(assert) { + const value = initialSelectionMode === 'single' ? new Date() : this.options.value; + this.reinit({ + value, + selectionMode: initialSelectionMode, + }); + + this.calendar.option('selectionMode', newSelectionMode); + + assert.deepEqual(this.calendar.option(optionName), expectedValue); + }); + + QUnit.test(`No cells should be selected after switching from ${initialSelectionMode} to ${newSelectionMode} selectionMode`, function(assert) { + const value = initialSelectionMode === 'single' ? new Date() : this.options.value; + this.reinit({ + value, + selectionMode: initialSelectionMode, + }); + + this.calendar.option('selectionMode', newSelectionMode); + + const $cells = $(getCurrentViewInstance(this.calendar).$element().find(`.${CALENDAR_SELECTED_DATE_CLASS}`)); + + assert.strictEqual($cells.length, 0); + }); + }); + + QUnit.module('SelectWeekOnClick', { + beforeEach: function() { + this.initialValue = ['2023/08/08', '2023/08/16', '2023/08/20']; + } + }, () => { + ['multiple', 'range'].forEach((selectionMode) => { + ['init', 'runtime'].forEach((scenario) => { + QUnit.test(`Click on week number should select week (selectionMode=${selectionMode};selectWeekOnClick=true on ${scenario})`, function(assert) { + this.reinit({ + value: this.initialValue, + selectionMode, + selectWeekOnClick: scenario === 'init', + showWeekNumbers: true, + }); + + if(scenario === 'runtime') { + this.calendar.option('selectWeekOnClick', true); + } + + const $row = this.$element.find('tr').eq(3); + const $weekNumberCell = $row.find(`.${CALENDAR_WEEK_NUMBER_CELL_CLASS}`); + const firstDateInRow = dataUtils.data($row.find(`.${CALENDAR_CELL_CLASS}`).first().get(0), CALENDAR_DATE_VALUE_KEY); + const lastDateInRow = dataUtils.data($row.find(`.${CALENDAR_CELL_CLASS}`).last().get(0), CALENDAR_DATE_VALUE_KEY); + + $weekNumberCell.trigger('dxclick'); + + const value = this.calendar.option('value'); + const expectedValueLength = selectionMode === 'multiple' ? 7 : 2; + + assert.strictEqual(value.length, expectedValueLength, `${value.length} days are selected`); + assert.deepEqual(value[0], firstDateInRow, 'fisrt selected date is first date in row'); + assert.deepEqual(value[value.length - 1], lastDateInRow, 'last selected date is last date in row'); + }); + + QUnit.test(`Click on week number should not select week (selectionMode=${selectionMode};selectWeekOnClick=false on ${scenario})`, function(assert) { + this.reinit({ + value: this.initialValue, + selectionMode, + selectWeekOnClick: scenario !== 'init', + showWeekNumbers: true, + }); + + if(scenario === 'runtime') { + this.calendar.option('selectWeekOnClick', false); + } + + const $row = this.$element.find('tr').eq(3); + const $weekNumberCell = $row.find(`.${CALENDAR_WEEK_NUMBER_CELL_CLASS}`); + + $weekNumberCell.trigger('dxclick'); + + const value = this.calendar.option('value'); + + assert.deepEqual(value, this.initialValue, 'values are not changed'); + }); + }); + + QUnit.test(`Click on week number should select nothing when all dates are disabled (selectionMode=${selectionMode})`, function(assert) { + this.reinit({ + selectionMode, + showWeekNumbers: true, + disabledDates: () => true, + }); + + const $row = this.$element.find('tr').eq(3); + const $weekNumberCell = $row.find(`.${CALENDAR_WEEK_NUMBER_CELL_CLASS}`); + + $weekNumberCell.trigger('dxclick'); + + const value = this.calendar.option('value'); + const expectedValue = selectionMode === 'range' ? [null, null] : []; + + assert.deepEqual(value, expectedValue, 'no dates are selected'); + }); + + QUnit.test(`Click on week number should not select dates that are less than min/bigger than max (selectionMode=${selectionMode})`, function(assert) { + const date = new Date('2023/09/05'); + this.reinit({ + selectionMode, + showWeekNumbers: true, + currentDate: date, + min: date, + max: date, + }); + + const $row = this.$element.find('tr').eq(2); + const $weekNumberCell = $row.find(`.${CALENDAR_WEEK_NUMBER_CELL_CLASS}`); + + $weekNumberCell.trigger('dxclick'); + + const value = this.calendar.option('value'); + const expectedValue = selectionMode === 'multiple' ? [date] : [date, date]; + + assert.deepEqual(value, expectedValue); + }); + }); + + QUnit.test('Click on week number should not select disabled dates in multiple selectionMode', function(assert) { + this.reinit({ + selectionMode: 'multiple', + showWeekNumbers: true, + disabledDates: ({ date }) => date.getDay() !== 0, + }); + + const $row = this.$element.find('tr').eq(3); + const $weekNumberCell = $row.find(`.${CALENDAR_WEEK_NUMBER_CELL_CLASS}`); + + $weekNumberCell.trigger('dxclick'); + + const value = this.calendar.option('value'); + + assert.strictEqual(value.length, 1, 'only one day is selected'); + }); + + QUnit.test('Click on week number should select dates correctly when min/max=null (selectionMode=multiple)', function(assert) { + this.reinit({ + selectionMode: 'multiple', + showWeekNumbers: true, + min: null, + max: null, + }); + + const $row = this.$element.find('tr').eq(2); + const $weekNumberCell = $row.find(`.${CALENDAR_WEEK_NUMBER_CELL_CLASS}`); + + $weekNumberCell.trigger('dxclick'); + + const valueLength = this.calendar.option('value').length; + + assert.deepEqual(valueLength, 7, 'week is selected'); + }); + + QUnit.test('Click on week number should select range from first available date to last available date', function(assert) { + this.reinit({ + selectionMode: 'range', + showWeekNumbers: true, + firstDayOfWeek: 0, + disabledDates: ({ date }) => { + const day = date.getDay(); + return day === 0 || day === 6 || day === 3; + } + }); + + const $row = this.$element.find('tr').eq(3); + const $weekNumberCell = $row.find(`.${CALENDAR_WEEK_NUMBER_CELL_CLASS}`); + const firstDateInRow = dataUtils.data($row.find(`.${CALENDAR_CELL_CLASS}`).first().get(0), CALENDAR_DATE_VALUE_KEY); + const firstAvailableDateInRow = dataUtils.data($row.find(`.${CALENDAR_CELL_CLASS}`).eq(1).get(0), CALENDAR_DATE_VALUE_KEY); + const lastDateInRow = dataUtils.data($row.find(`.${CALENDAR_CELL_CLASS}`).last().get(0), CALENDAR_DATE_VALUE_KEY); + const lastAvailableDateInRow = dataUtils.data($row.find(`.${CALENDAR_CELL_CLASS}`).eq(5).get(0), CALENDAR_DATE_VALUE_KEY); + + $weekNumberCell.trigger('dxclick'); + + const value = this.calendar.option('value'); + + assert.notDeepEqual(value, [firstDateInRow, lastDateInRow], 'disabled dates are not selected as range start/end'); + assert.deepEqual(value, [firstAvailableDateInRow, lastAvailableDateInRow], 'first/last available dates are range start/end'); + }); + + [ + { + selectionMode: 'single', + selectWeekOnClick: true, + expectedCursor: 'auto', + }, + { + selectionMode: 'single', + selectWeekOnClick: false, + expectedCursor: 'auto', + }, + { + selectionMode: 'multiple', + selectWeekOnClick: false, + expectedCursor: 'auto', + }, + { + selectionMode: 'range', + selectWeekOnClick: false, + expectedCursor: 'auto', + }, + { + selectionMode: 'multiple', + selectWeekOnClick: true, + expectedCursor: 'pointer', + }, + { + selectionMode: 'range', + selectWeekOnClick: true, + expectedCursor: 'pointer', + } + ].forEach(({ selectionMode, selectWeekOnClick, expectedCursor }) => { + QUnit.test(`Week number should have "cursor: ${expectedCursor}" style (selectionMode=${selectionMode};selectWeekOnClick=${selectWeekOnClick})`, function(assert) { + this.reinit({ + selectionMode, + selectWeekOnClick, + showWeekNumbers: true, + }); + const cursor = this.$element.find(`.${CALENDAR_WEEK_NUMBER_CELL_CLASS}`).first().css('cursor'); + + assert.strictEqual(cursor, expectedCursor); + }); + }); + }); + }); + + QUnit.module('ViewsCount = 2', { + beforeEach: function() { + this.options = { + focusStateEnabled: true, + value: [new Date('01/15/2023'), new Date('02/05/2023')], + selectionMode: 'range', + viewsCount: 2, + }; + this.reinit(this.options); + this.viewWidth = this.calendar._viewWidth(); + } + }, () => { + QUnit.test('Calendar should not have additional view after runtime multiview disable', function(assert) { + this.calendar.option('viewsCount', 1); + + const additionalView = getAdditionalViewInstance(this.calendar); + + assert.notOk(additionalView); + }); + + QUnit.test('Calendar should have additional view after runtime multiview enable', function(assert) { + this.reinit({ + ...this.options, + viewsCount: 1 + }); + + this.calendar.option('viewsCount', 2); + const additionalView = getAdditionalViewInstance(this.calendar); + + assert.ok(additionalView, undefined); + }); + + QUnit.test('Click on date in additinal view should not trigger views movement', function(assert) { + let $cell = $(getAdditionalViewInstance(this.calendar).$element().find('*[data-value="2023/02/16"]')); + + $cell.trigger('dxclick'); + + $cell = $(getAdditionalViewInstance(this.calendar).$element().find('*[data-value="2023/02/16"]')); + + assert.strictEqual($cell.length, 1); + }); + + QUnit.test('Click on next month date in additinal view should trigger views movement', function(assert) { + let $cell = $(getAdditionalViewInstance(this.calendar).$element().find('*[data-value="2023/03/03"]')); + + $cell.trigger('dxclick'); + + $cell = $(getAdditionalViewInstance(this.calendar).$element().find('*[data-value="2023/02/16"]')); + + assert.strictEqual($cell.length, 0); + }); + + QUnit.test('contouredDate should be moved to additional view after keyboard moving from the last date on main view', function(assert) { + const $cell = $(getCurrentViewInstance(this.calendar).$element().find('*[data-value="2023/01/31"]')); + const keyboard = keyboardMock($(this.calendar._$viewsWrapper)); + + $cell.trigger('dxclick'); + keyboard.press('right'); + + const viewContouredDate = getCurrentViewInstance(this.calendar).option('contouredDate'); + const additionalViewContouredDate = getAdditionalViewInstance(this.calendar).option('contouredDate'); + + assert.strictEqual(viewContouredDate, null); + assert.deepEqual(additionalViewContouredDate, new Date('2023/02/01')); + }); + + [ + { + offset: 2, + button: 'next', + focusedView: 'main' + }, + { + offset: 1, + button: 'next', + focusedView: 'additional' + }, + { + offset: -1, + button: 'previous', + focusedView: 'main' + }, + { + offset: -2, + button: 'previous', + focusedView: 'additional' + }, + ].forEach(({ offset, button, focusedView }) => { + QUnit.test(`Click on ${button} month button should change currentDate on ${offset} months if ${focusedView} view is focused`, function(assert) { + if(focusedView === 'additional') { + const $additionalViewCell = $(getAdditionalViewInstance(this.calendar).$element().find('*[data-value="2023/02/16"]')); + + $additionalViewCell.trigger('dxclick'); + } + + const currentDate = this.calendar.option('currentDate'); + const navigatorButtonClass = button === 'next' ? CALENDAR_NAVIGATOR_NEXT_VIEW_CLASS : CALENDAR_NAVIGATOR_PREVIOUS_VIEW_CLASS; + const $navigatorButton = this.$element.find(`.${navigatorButtonClass}`); + + $navigatorButton.trigger('dxclick'); + + const newCurrentDate = this.calendar.option('currentDate'); + const expectedCurrentDate = new Date(currentDate.setMonth(currentDate.getMonth() + offset)); + + assert.deepEqual(newCurrentDate, expectedCurrentDate); + }); + }); + + + [ + { + currentDate: new Date('04/15/2023'), + offset: 3, + shouldRefresh: true + }, + { + currentDate: new Date('11/15/2022'), + offset: -2, + shouldRefresh: true + }, + { + currentDate: new Date('03/15/2023'), + offset: 2, + shouldRefresh: false + }, + { + currentDate: new Date('12/15/2022'), + offset: -1, + shouldRefresh: false + } + ].forEach(({ currentDate, offset, shouldRefresh }) => { + QUnit.test(`Views should ${shouldRefresh ? '' : 'not'} be refreshed if currentDate change offset is than ${offset} months`, function(assert) { + const spy = sinon.spy(this.calendar, '_refreshViews'); + + this.calendar.option('currentDate', currentDate); + + assert.strictEqual(spy.calledOnce, shouldRefresh); + }); + }); + + [false, true].forEach((rtlEnabled) => { + QUnit.test(`Should double currentDate change on ${rtlEnabled ? 'right' : 'left'} swipe if additionalView is active (rtlEnabled=${rtlEnabled})`, function(assert) { + const calendar = this.calendar; + calendar.option('rtlEnabled', rtlEnabled); + const $cell = $(getAdditionalViewInstance(calendar).$element().find('*[data-value="2023/02/16"]')); + + $cell.trigger('dxclick'); + const currentDate = calendar.option('currentDate'); + const pointer = pointerMock(this.$element).start(); + + pointer.swipeStart().swipeEnd(0.5 * rtlEnabled ? -1 : 1); + + const expectedCurrentDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - 2, currentDate.getDate()); + const newCurrentDate = calendar.option('currentDate'); + + assert.deepEqual(newCurrentDate, expectedCurrentDate); + }); + }); + }); +}); + + +QUnit.module('ZoomLevel option', { + beforeEach: function() { + fx.off = true; + this.$element = $('
').appendTo('#qunit-fixture'); + }, + afterEach: function() { + this.$element.remove(); + fx.off = false; + } +}, () => { + QUnit.test('\'zoomLevel\' should have correct value on init if \'maxZoomLevel\' is specified', function(assert) { + const calendar = this.$element.dxCalendar({ + maxZoomLevel: 'year', + zoomLevel: 'month' + }).dxCalendar('instance'); + + assert.equal(calendar.option('zoomLevel'), calendar.option('maxZoomLevel'), '\'zoomLevel\' is corrected'); + }); + + QUnit.test('view should not be changed down if specified maxZoomLevel is reached', function(assert) { + const calendar = this.$element.dxCalendar({ + maxZoomLevel: 'year', + zoomLevel: 'decade' + }).dxCalendar('instance'); + + $(this.$element.find(`.${CALENDAR_CELL_CLASS}`).eq(5)).trigger('dxclick'); + assert.equal(calendar.option('zoomLevel'), 'year', '\'zoomLevel\' changed'); + + $(this.$element.find(`.${CALENDAR_CELL_CLASS}`).eq(5)).trigger('dxclick'); + assert.equal(calendar.option('zoomLevel'), 'year', '\'zoomLevel\' did not change'); + }); + + QUnit.test('\'zoomLevel\' should be aligned after \'maxZoomLevel\' option change if out of bounds', function(assert) { + const calendar = this.$element.dxCalendar({ + maxZoomLevel: 'month', + value: new Date(2015, 2, 15) + }).dxCalendar('instance'); + + $.each(['month', 'year', 'decade', 'century'], (_, type) => { + calendar.option('maxZoomLevel', type); + + assert.equal(calendar.option('zoomLevel'), type, 'calendar \'zoomLevel\' is correct'); + }); + }); + + QUnit.test('\'zoomLevel\' option should not be changed after \'maxZoomLevel\' option change', function(assert) { + const calendar = this.$element.dxCalendar({ + maxZoomLevel: 'century', + value: new Date(2015, 2, 15) + }).dxCalendar('instance'); + + $.each(['month', 'year', 'decade', 'century'], (_, type) => { + calendar.option('maxZoomLevel', type); + + assert.equal(calendar.option('zoomLevel'), 'century', 'calendar \'zoomLevel\' is correct'); + }); + }); + + QUnit.test('calendar should get correct value after click on cell of specified maxZoomLevel', function(assert) { + const calendar = this.$element.dxCalendar({ + maxZoomLevel: 'year', + value: new Date(2015, 2, 15) + }).dxCalendar('instance'); + + $(this.$element.find(`.${CALENDAR_CELL_CLASS}`).eq(5)).trigger('dxclick'); + assert.deepEqual(calendar.option('value'), new Date(2015, 5, 1), '\'zoomLevel\' changed'); + + calendar.option('maxZoomLevel', 'decade'); + $(this.$element.find(`.${CALENDAR_CELL_CLASS}`).eq(5)).trigger('dxclick'); + assert.deepEqual(calendar.option('value'), new Date(2014, 0, 1), '\'zoomLevel\' changed'); + + calendar.option('maxZoomLevel', 'century'); + $(this.$element.find(`.${CALENDAR_CELL_CLASS}`).eq(5)).trigger('dxclick'); + assert.deepEqual(calendar.option('value'), new Date(2040, 0, 1), '\'zoomLevel\' changed'); + }); + + QUnit.test('do not go up if minZoomLevel is reached', function(assert) { + const $element = this.$element; + const instance = $element.dxCalendar().dxCalendar('instance'); + + $.each(['month', 'year', 'decade'], (_, type) => { + instance.option({ + minZoomLevel: type, + zoomLevel: type + }); + + $(`.${CALENDAR_CAPTION_BUTTON_CLASS}`).trigger('dxclick'); + assert.equal(instance.option('zoomLevel'), type, 'zoom level did not change'); + }); + }); + + QUnit.test('\'zoomLevel\' should be aligned after \'minZoomLevel\' option change if out of bounds', function(assert) { + const $element = this.$element; + const instance = $element.dxCalendar({ + minZoomLevel: 'century', + zoomLevel: 'century' + }).dxCalendar('instance'); + + $.each(['decade', 'year', 'month'], (_, type) => { + instance.option('minZoomLevel', type); + assert.equal(instance.option('zoomLevel'), type, 'zoom level is changed correctly'); + }); + }); + + QUnit.test('cancel change zoomLevel if there is only one cell on new view', function(assert) { + const calendar = this.$element.dxCalendar({ + maxZoomLevel: 'month', + min: new Date(2015, 3, 5), + max: new Date(2015, 3, 25), + value: new Date(2015, 2, 15) + }).dxCalendar('instance'); + + const $captionButton = this.$element.find(`.${CALENDAR_CAPTION_BUTTON_CLASS}`); + + $($captionButton).trigger('dxclick'); + assert.equal(calendar.option('zoomLevel'), 'month', 'view is not changed (month)'); + + calendar.option('zoomLevel', 'year'); + calendar.option('max', new Date(2015, 6, 25)); + $($captionButton).trigger('dxclick'); + assert.equal(calendar.option('zoomLevel'), 'year', 'view is not changed (year)'); + + calendar.option('zoomLevel', 'decade'); + calendar.option('max', new Date(2017, 6, 25)); + $($captionButton).trigger('dxclick'); + assert.equal(calendar.option('zoomLevel'), 'decade', 'view is not changed (decade)'); + }); + + QUnit.test('change ZoomLevel after click on view cell', function(assert) { + const $element = this.$element; + const calendar = $element.dxCalendar({ + zoomLevel: 'century', + value: new Date(2015, 2, 15) + }).dxCalendar('instance'); + + $.each(['century', 'decade'], (_, type) => { + calendar.option('zoomLevel', type); + + $($element.find(`.${CALENDAR_CELL_CLASS}`).not(`.${CALENDAR_OTHER_VIEW_CLASS}`).eq(3)).trigger('dxclick'); + assert.notStrictEqual(calendar.option('zoomLevel'), type, 'zoomLevel option view is changed'); + }); + }); + + QUnit.test('change ZoomLevel after pressing enter key on view cell', function(assert) { + const $element = this.$element; + const calendar = $element.dxCalendar({ + zoomLevel: 'century', + value: new Date(2015, 2, 15), + focusStateEnabled: true + }).dxCalendar('instance'); + + $.each(['century', 'decade'], (_, type) => { + calendar.option('zoomLevel', type); + calendar.focus(); + triggerKeydown($(calendar._$viewsWrapper), ENTER_KEY_CODE); + assert.notStrictEqual(calendar.option('zoomLevel'), type, 'zoomLevel option view is changed'); + }); + }); + + QUnit.test('change ZoomLevel after click on other view cell', function(assert) { + const $element = this.$element; + const calendar = $element.dxCalendar({ + zoomLevel: 'century', + value: new Date(2015, 2, 15) + }).dxCalendar('instance'); + + $.each(['century', 'decade'], (_, type) => { + calendar.option('zoomLevel', type); + + $($element.find(`.${CALENDAR_OTHER_VIEW_CLASS}`).first()).trigger('dxclick'); + assert.notStrictEqual(calendar.option('zoomLevel'), type, 'zoomLevel option view is changed'); + }); + }); + + QUnit.test('Current view should be set correctly, after click on other view cells', function(assert) { + + const $element = this.$element; + const calendar = $element.dxCalendar({ + value: new Date(2015, 1, 1), + zoomLevel: 'decade' + }).dxCalendar('instance'); + + const spy = sinon.spy(calendar, '_navigate'); + + try { + fx.off = false; + this.clock = sinon.useFakeTimers(); + $($element.find(`.${CALENDAR_CELL_CLASS}`).first()).trigger('dxclick'); + + this.clock.tick(1000); + + const navigatorCaptionText = $element.find(`.${CALENDAR_CAPTION_BUTTON_CLASS}`).text(); + const dataCell = $element.find(`.${CALENDAR_CELL_CLASS}`).first().data('value'); + + assert.equal(navigatorCaptionText, '2009', 'navigator caption text is correct'); + assert.equal(dataCell, '2009/01/01', 'cell data is correct'); + assert.ok(!spy.called, '_navigate should not be called'); + assert.equal(calendar.option('zoomLevel'), 'year'); + } finally { + fx.off = true; + this.clock.restore(); + } + }); + + QUnit.test('Month names should be shown in \'abbreviated\' format when ZoomLevel is Year', function(assert) { + const getMonthNamesStub = sinon.stub(dateLocalization, 'getMonthNames'); + + getMonthNamesStub.returns(['leden', 'únor', 'březen', 'duben', 'květen', 'červen', 'červenec', 'srpen', 'září', 'říjen', 'listopad', 'prosinec']); + getMonthNamesStub.withArgs('abbreviated').returns(['led', 'úno', 'bře', 'dub', 'kvě', 'čvn', 'čvc', 'srp', 'zář', 'říj', 'lis', 'pro']); + + const calendar = this.$element.dxCalendar({ + zoomLevel: 'year', + value: new Date(2017, 10, 20) + }).dxCalendar('instance'); + + const $cells = $(getCurrentViewInstance(calendar).$element().find('.dx-calendar-cell')); + + assert.equal($cells.eq(5).text().trim(), 'čvn'); + assert.equal($cells.eq(6).text().trim(), 'čvc'); + + getMonthNamesStub.restore(); + }); +}); + + +QUnit.module('Min & Max options', { + beforeEach: function() { + fx.off = true; + + this.value = new Date(2010, 10, 10); + this.minDate = new Date(2010, 9, 10); + this.maxDate = new Date(2010, 11, 10); + + this.$element = $('
').appendTo('#qunit-fixture'); + this.calendar = this.$element.dxCalendar({ + min: this.minDate, + value: this.value, + max: this.maxDate, + focusStateEnabled: true + }).dxCalendar('instance'); + + this.clock = sinon.useFakeTimers(); + + this.reinit = (options) => { + this.$element.remove(); + this.$element = $('
').appendTo('#qunit-fixture'); + this.calendar = this.$element.dxCalendar(options).dxCalendar('instance'); + }; + }, + afterEach: function() { + this.$element.remove(); + this.clock.restore(); + fx.off = false; + } +}, () => { + QUnit.test('calendar should not throw error if max date is null', function(assert) { + assert.expect(0); + + new Calendar('
', { value: new Date(2013, 9, 15), firstDayOfWeek: 1, max: null }); + }); + + QUnit.test('calendar must pass min and max to the created views', function(assert) { + assert.deepEqual(getCurrentViewInstance(this.calendar).option('min'), this.minDate); + assert.deepEqual(getCurrentViewInstance(this.calendar).option('max'), this.maxDate); + }); + + QUnit.test('calendar should not allow to navigate to a date earlier than min and later than max via keyboard events', function(assert) { + const isAnimationOff = fx.off; + const animate = fx.animate; + + try { + let animateCount = 0; + + fx.off = false; + + fx.animate = (...args) => { + animateCount++; + return animate.apply(fx, args); + }; + + const minimumCurrentDate = new Date(this.value.getFullYear(), this.value.getMonth() - 1, this.value.getDate()); + const currentDate = new Date(this.value.getFullYear(), this.value.getMonth(), this.value.getDate()); + const maximumCurrentDate = new Date(this.value.getFullYear(), this.value.getMonth() + 1, this.value.getDate()); + + const calendar = this.calendar; + const $viewsWrapper = $(calendar._$viewsWrapper); + + calendar.focus(); + + triggerKeydown($viewsWrapper, PAGE_UP_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(this.calendar.option('currentDate'), minimumCurrentDate); + assert.equal(animateCount, 1, 'view is changed with animation after the \'page up\' key press the first time'); + + triggerKeydown($viewsWrapper, PAGE_UP_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(this.calendar.option('currentDate'), minimumCurrentDate); + assert.equal(animateCount, 1, 'view is not changed after the \'page up\' key press the second time'); + + triggerKeydown($viewsWrapper, PAGE_DOWN_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(this.calendar.option('currentDate'), currentDate); + + triggerKeydown($viewsWrapper, PAGE_DOWN_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(this.calendar.option('currentDate'), maximumCurrentDate); + assert.equal(animateCount, 3, 'view is changed with animation after the \'page down\' key press the first time'); + + triggerKeydown($viewsWrapper, PAGE_DOWN_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(this.calendar.option('currentDate'), maximumCurrentDate); + assert.equal(animateCount, 3, 'view is not changed after the \'page down\' key press the second time'); + } finally { + fx.off = isAnimationOff; + fx.animate = animate; + } + }); + + QUnit.test('calendar should set currentDate to min when setting to an earlier date; and to max when setting to a later date', function(assert) { + const calendar = this.calendar; + const min = calendar.option('min'); + const max = calendar.option('max'); + const earlyDate = new Date(this.minDate.getFullYear(), this.minDate.getMonth() - 1, 1); + const lateDate = new Date(this.maxDate.getFullYear(), this.maxDate.getMonth() + 1, 1); + + calendar.option('currentDate', earlyDate); + assert.deepEqual(calendar.option('currentDate'), new Date(this.minDate.getFullYear(), this.minDate.getMonth(), min.getDate())); + calendar.option('currentDate', lateDate); + assert.deepEqual(calendar.option('currentDate'), new Date(this.maxDate.getFullYear(), this.maxDate.getMonth(), max.getDate())); + }); + + QUnit.test('calendar should properly initialize currentDate with respect to min and max', function(assert) { + this.reinit({ + min: this.minDate, + max: this.maxDate + }); + + const calendar = this.calendar; + assert.ok(dateUtils.sameView(calendar.option('zoomLevel'), calendar.option('currentDate'), this.minDate)); + }); + + QUnit.test('value should not be changed when min and max options are set', function(assert) { + const calendar = this.calendar; + const outOfRangeDate = new Date(2010, 12, 10); + + calendar.option('value', outOfRangeDate); + assert.equal(calendar.option('value'), outOfRangeDate, 'value is not changed'); + }); + + QUnit.test('current date is max month if value is null and range is earlier than today', function(assert) { + this.reinit({ + min: this.minDate, + max: this.maxDate, + currentDate: new Date(2015, 10, 13), + value: null + }); + + const calendar = this.calendar; + + assert.strictEqual(calendar.option('value'), null, 'value is null'); + assert.deepEqual(calendar.option('currentDate'), new Date(this.maxDate), 'current date is max'); + }); + + QUnit.test('change currentDate without navigation if became out of range after max is set', function(assert) { + this.reinit({ + value: new Date(2015, 5, 16) + }); + + const spy = sinon.spy(this.calendar, '_navigate'); + const max = new Date(2015, 4, 7); + + this.calendar.option('max', max); + assert.deepEqual(this.calendar.option('currentDate'), max, 'currentDate and max are equal'); + assert.equal(spy.callCount, 0, 'there was no navigation'); + assert.equal(this.$element.find(`.${CALENDAR_CAPTION_BUTTON_CLASS}`).text(), 'May 2015', 'navigator caption is changed'); + }); + + QUnit.test('change currentDate without navigation if became out of range after min is set', function(assert) { + this.reinit({ + value: new Date(2015, 5, 16) + }); + + const spy = sinon.spy(this.calendar, '_navigate'); + const min = new Date(2015, 6, 12); + + this.calendar.option('min', min); + assert.deepEqual(this.calendar.option('currentDate'), min, 'currentDate and min are equal'); + assert.equal(spy.callCount, 0, 'there was no navigation'); + assert.equal(this.$element.find(`.${CALENDAR_CAPTION_BUTTON_CLASS}`).text(), 'July 2015', 'navigator caption is changed'); + }); + + QUnit.test('current date is not changed when min or max option is changed and current value is in range', function(assert) { + const value = new Date(2015, 0, 27); + + this.reinit({ + min: null, + max: null, + value: value + }); + + const calendar = this.calendar; + const minDate = new Date(value); + const maxDate = new Date(value); + + minDate.setYear(2014); + maxDate.setYear(2015); + + assert.deepEqual(calendar.option('currentDate'), value, 'current date and value are the same'); + + calendar.option('min', minDate); + assert.deepEqual(calendar.option('currentDate'), value, 'current date and min are the same after min option is set'); + assert.deepEqual(calendar.option('value'), value, 'value is not changed'); + + calendar.option('min', null); + assert.deepEqual(calendar.option('currentDate'), value, 'current date and value are the same'); + assert.deepEqual(calendar.option('value'), value, 'value is not changed'); + + calendar.option('max', maxDate); + assert.deepEqual(calendar.option('currentDate'), value, 'current date and max are the same after max option is set'); + assert.deepEqual(calendar.option('value'), value, 'value is not changed'); + }); + + QUnit.test('T278441 - min date should be 1/1/1000 if the \'min\' option is null', function(assert) { + const value = new Date(988, 7, 17); + + this.reinit({ + value: value, + min: null + }); + + assert.deepEqual(this.calendar.option('currentDate'), new Date(1000, 0), 'current date is correct'); + }); + + QUnit.test('T278441 - max date should be 31/12/2999 if the \'max\' option is null', function(assert) { + const value = new Date(3015, 7, 17); + + this.reinit({ + value: value, + max: null + }); + + assert.deepEqual(this.calendar.option('currentDate'), new Date(3000, 0), 'current date is correct'); + }); + + QUnit.test('T266658 - widget should have no views that are out of range', function(assert) { + this.reinit({ + value: new Date(2015, 8, 8), + min: new Date(2015, 8, 2), + max: new Date(2015, 9, 20) + }); + + const calendar = this.calendar; + const $viewsWrapper = $(calendar.$element().find(`.${CALENDAR_VIEWS_WRAPPER_CLASS}`)); + + assert.equal($viewsWrapper.children().length, 2, 'the number of views is correct when current view contain min date'); + assert.ok(!getBeforeViewInstance(calendar), 'there is no after view'); + + calendar.option('value', new Date(2015, 9, 15)); + + assert.equal($viewsWrapper.children().length, 2, 'the number of views is correct when current view contain max date'); + assert.ok(!getAfterViewInstance(calendar), 'there is no after view'); + }); + + QUnit.test('T266658 - widget should have no views that are out of range after navigation', function(assert) { + this.reinit({ + value: new Date(2015, 9, 8), + min: new Date(2015, 8, 2), + max: new Date(2015, 9, 20) + }); + + const calendar = this.calendar; + const $views = $(calendar.$element().find(`.${CALENDAR_VIEWS_WRAPPER_CLASS}`).children()); + + assert.equal($views.length, 2, 'the number of views is correct when current view contain min date'); + }); + + QUnit.test('correct views rendering with min option', function(assert) { + const params = { + 'year': { value: new Date(2015, 0, 8), min: new Date(2014, 11, 16) }, + 'decade': { value: new Date(2010, 0, 8), min: new Date(2009, 11, 16) }, + 'century': { value: new Date(2000, 0, 8), min: new Date(1999, 11, 16) } + }; + + $.each(['year', 'decade', 'century'], $.proxy((_, type) => { + this.reinit($.extend({}, params[type], { zoomLevel: type })); + + const $views = this.$element.find(`.${CALENDAR_VIEWS_WRAPPER_CLASS}`).children(); + assert.equal($views.length, 3, 'all three views are rendered'); + }, this)); + }); + + QUnit.test('correct views rendering with max option', function(assert) { + const params = { + 'year': { value: new Date(2015, 11, 8), max: new Date(2016, 0, 16) }, + 'decade': { value: new Date(2019, 11, 8), max: new Date(2020, 0, 16) }, + 'century': { value: new Date(2099, 11, 8), max: new Date(2100, 0, 16) } + }; + + $.each(['year', 'decade', 'century'], $.proxy((_, type) => { + this.reinit($.extend({}, params[type], { zoomLevel: type })); + + const $views = this.$element.find(`.${CALENDAR_VIEWS_WRAPPER_CLASS}`).children(); + assert.equal($views.length, 3, 'all three views are rendered'); + }, this)); + }); +}); + + +QUnit.module('disabledDates option', { + beforeEach: function() { + fx.off = true; + + this.value = new Date(2010, 10, 10); + this.disabledDates = (args) => { + const month = args.date.getMonth(); + + if(month === 9 || month === 11) { + return true; + } + }; + + this.$element = $('
').appendTo('#qunit-fixture'); + this.calendar = this.$element.dxCalendar({ + disabledDates: this.disabledDates, + value: this.value, + focusStateEnabled: true + }).dxCalendar('instance'); + + this.clock = sinon.useFakeTimers(); + + this.reinit = (options) => { + this.$element.remove(); + this.$element = $('
').appendTo('#qunit-fixture'); + this.calendar = this.$element.dxCalendar(options).dxCalendar('instance'); + }; + }, + afterEach: function() { + this.$element.remove(); + this.clock.restore(); + fx.off = false; + } +}, () => { + QUnit.test('navigating to the disabled month should not skip the month and should focus the current date', function(assert) { + const isAnimationOff = fx.off; + const animationSpy = sinon.spy(fx, 'animate'); + + try { + fx.off = false; + + const calendar = this.calendar; + const $viewsWrapper = $(calendar._$viewsWrapper); + + calendar.focus(); + + triggerKeydown($viewsWrapper, PAGE_UP_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), new Date(2010, 9, 10), 'same date has been focused'); + assert.equal(animationSpy.callCount, 1, 'view is changed with animation after the \'page up\' key press the first time'); + + triggerKeydown($viewsWrapper, PAGE_UP_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), new Date(2010, 8, 10), 'same date has been focused'); + assert.equal(animationSpy.callCount, 2, 'view is changed after the \'page up\' key press the second time'); + + triggerKeydown($viewsWrapper, PAGE_DOWN_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), new Date(2010, 9, 10), 'same date has been focused'); + assert.equal(animationSpy.callCount, 3, 'view is changed with animation after the \'page down\' key press the first time'); + + triggerKeydown($viewsWrapper, PAGE_DOWN_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), new Date(2010, 10, 10), 'same date has been focused'); + assert.equal(animationSpy.callCount, 4, 'view is changed with animation after the \'page down\' key press the first time'); + + triggerKeydown($viewsWrapper, PAGE_DOWN_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), new Date(2010, 11, 10), 'same date has been focused'); + assert.equal(animationSpy.callCount, 5, 'view is changed after the \'page down\' key press the third time'); + + $(this.$element.find(`.${CALENDAR_NAVIGATOR_PREVIOUS_VIEW_CLASS}`)).trigger('dxclick'); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(this.calendar.option('currentDate'), new Date(2010, 10, 10), 'same date has been focused'); + assert.equal(animationSpy.callCount, 6, 'view is changed after the click on previous arrow on UI'); + + $(this.$element.find(`.${CALENDAR_NAVIGATOR_NEXT_VIEW_CLASS}`)).trigger('dxclick'); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), new Date(2010, 11, 10), 'same date has been focused'); + assert.equal(animationSpy.callCount, 7, 'view is changed after the click on next arrow on UI'); + } finally { + fx.off = isAnimationOff; + animationSpy.restore(); + } + }); + + QUnit.test('navigating to next/previous month should focus the closest available date and change the view', function(assert) { + const isAnimationOff = fx.off; + const animationSpy = sinon.spy(fx, 'animate'); + + try { + const calendar = this.calendar; + calendar.option({ + value: new Date(2020, 0, 15), + disabledDates: (args) => { + const date = args.date.getDate(); + const month = args.date.getMonth(); + return month === 0 && date >= 20 || month === 1 && date < 20; + } + }); + const $viewsWrapper = $(calendar._$viewsWrapper); + + fx.off = false; + animationSpy.resetHistory(); + + const lastAvailableDateOnJanuary = new Date(2020, 0, 19); + const firstAvailableDateOnFebruary = new Date(2020, 1, 20); + calendar.focus(); + + triggerKeydown($viewsWrapper, PAGE_DOWN_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), firstAvailableDateOnFebruary, 'closest available date has been focused'); + assert.equal(animationSpy.callCount, 1, 'view has been changed'); + + triggerKeydown($viewsWrapper, PAGE_UP_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), lastAvailableDateOnJanuary, 'closest available date has been focused'); + assert.equal(animationSpy.callCount, 2, 'view has been changed'); + + triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE, { ctrlKey: true }); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), firstAvailableDateOnFebruary, 'closest available date has been focused'); + assert.equal(animationSpy.callCount, 3, 'view has been changed'); + + triggerKeydown($viewsWrapper, LEFT_ARROW_KEY_CODE, { ctrlKey: true }); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), lastAvailableDateOnJanuary, 'closest available date has been focused'); + assert.equal(animationSpy.callCount, 4, 'view has been changed'); + + $(this.$element.find(`.${CALENDAR_NAVIGATOR_NEXT_VIEW_CLASS}`)).trigger('dxclick'); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), firstAvailableDateOnFebruary, 'closest available date has been focused'); + assert.equal(animationSpy.callCount, 5, 'view has been changed'); + + $(this.$element.find(`.${CALENDAR_NAVIGATOR_PREVIOUS_VIEW_CLASS}`)).trigger('dxclick'); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), lastAvailableDateOnJanuary, 'closest available date has been focused'); + assert.equal(animationSpy.callCount, 6, 'view has been changed'); + } finally { + fx.off = isAnimationOff; + animationSpy.restore(); + } + }); + + QUnit.test('left/right/up/downArrow should focus the closest date on the previous/next month when forced to change the month', function(assert) { + const isAnimationOff = fx.off; + const animationSpy = sinon.spy(fx, 'animate'); + + try { + const calendar = this.calendar; + + calendar.option({ + value: new Date(2020, 0, 14), + disabledDates: (args) => { + return args.date.getDate() >= 15 || args.date.getDate() <= 4; + } + }); + const $viewsWrapper = $(calendar._$viewsWrapper); + + fx.off = false; + animationSpy.resetHistory(); + + calendar.focus(); + + triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), new Date(2020, 1, 5), 'closest available date has been focused'); + assert.equal(animationSpy.callCount, 1, 'view has been changed'); + + triggerKeydown($viewsWrapper, LEFT_ARROW_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 14), 'closest available date has been focused'); + assert.equal(animationSpy.callCount, 2, 'view has been changed'); + + triggerKeydown($viewsWrapper, DOWN_ARROW_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), new Date(2020, 1, 11), 'closest available date has been focused'); + assert.equal(animationSpy.callCount, 3, 'view has been changed'); + + triggerKeydown($viewsWrapper, UP_ARROW_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 14), 'closest available date has been focused'); + assert.equal(animationSpy.callCount, 4, 'view has been changed'); + } finally { + fx.off = isAnimationOff; + animationSpy.restore(); + } + }); + + QUnit.test('left/right/up/downArrow should try focus the date moved by offset in a month', function(assert) { + const calendar = this.calendar; + + calendar.option({ + value: new Date(2020, 0, 6), + disabledDates: (args) => { + const date = args.date.getDate(); + return date > 10 && date < 16 || date === 7 || date === 21; + } + }); + const $viewsWrapper = $(calendar._$viewsWrapper); + + calendar.focus(); + + triggerKeydown($viewsWrapper, DOWN_ARROW_KEY_CODE); + assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 20), 'closest date by offset has been focused'); + + triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE); + assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 22), 'closest date by offset has been focused'); + + triggerKeydown($viewsWrapper, UP_ARROW_KEY_CODE); + assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 8), 'closest date by offset has been focused'); + + triggerKeydown($viewsWrapper, LEFT_ARROW_KEY_CODE); + assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 6), 'closest date by offset has been focused'); + }); + + QUnit.test('left/right arrows should try focus the month moved by offset in a year view', function(assert) { + const calendar = this.calendar; + + calendar.option({ + value: new Date(2020, 0, 6), + zoomLevel: 'year', + disabledDates: (args) => { + const month = args.date.getMonth(); + return month % 2; + } + }); + const $viewsWrapper = $(calendar._$viewsWrapper); + + calendar.focus(); + + triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE); + assert.deepEqual(calendar.option('currentDate'), new Date(2020, 2, 6), 'closest month by offset has been focused'); + + triggerKeydown($viewsWrapper, LEFT_ARROW_KEY_CODE); + assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 6), 'closest month by offset has been focused'); + }); + + QUnit.test('left/right/up/down arrows should try focus the year moved by offset in a decade view', function(assert) { + const calendar = this.calendar; + + calendar.option({ + value: new Date(2020, 0, 6), + zoomLevel: 'decade', + disabledDates: (args) => { + const year = args.date.getYear(); + return year === 121 || year === 124; + } + }); + const $viewsWrapper = $(calendar._$viewsWrapper); + + calendar.focus(); + + triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE); + assert.deepEqual(calendar.option('currentDate'), new Date(2022, 0, 6), 'closest year by offset has been focused'); + + triggerKeydown($viewsWrapper, LEFT_ARROW_KEY_CODE); + assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 6), 'closest year by offset has been focused'); + + triggerKeydown($viewsWrapper, DOWN_ARROW_KEY_CODE); + assert.deepEqual(calendar.option('currentDate'), new Date(2028, 0, 6), 'closest year by offset has been focused'); + + triggerKeydown($viewsWrapper, UP_ARROW_KEY_CODE); + assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 6), 'closest year by offset has been focused'); + }); + + QUnit.test('left/right/up/down arrows should try focus the decade moved by offset in a century view', function(assert) { + const calendar = this.calendar; + + calendar.option({ + value: new Date(2000, 0, 6), + zoomLevel: 'century', + disabledDates: (args) => { + const year = args.date.getYear(); + return year >= 110 && year < 120 || year >= 140 && year < 150; + } + }); + const $viewsWrapper = $(calendar._$viewsWrapper); + + calendar.focus(); + + triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE); + assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 6), 'closest decade by offset has been focused'); + + triggerKeydown($viewsWrapper, LEFT_ARROW_KEY_CODE); + assert.deepEqual(calendar.option('currentDate'), new Date(2000, 0, 6), 'closest decade by offset has been focused'); + + triggerKeydown($viewsWrapper, DOWN_ARROW_KEY_CODE); + assert.deepEqual(calendar.option('currentDate'), new Date(2080, 0, 6), 'closest decade by offset has been focused'); + + triggerKeydown($viewsWrapper, UP_ARROW_KEY_CODE); + assert.deepEqual(calendar.option('currentDate'), new Date(2000, 0, 6), 'closest decade by offset has been focused'); + }); + + QUnit.test('disabled decade/century should not be skipped during navigation', function(assert) { + const calendar = this.calendar; + + calendar.option({ + value: new Date(2020, 0, 6), + zoomLevel: 'decade', + disabledDates: (args) => { + const view = args.view; + const year = args.date.getYear(); + if(view === 'decade') { + return year >= 130 && year < 140; + } else if(view === 'century') { + return year >= 100 && year < 200; + } + } + }); + const $viewsWrapper = $(calendar._$viewsWrapper); + + calendar.focus(); + + triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE, { ctrlKey: true }); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), new Date(2030, 0, 6), 'current date is correct'); + + triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), new Date(2040, 0, 6), 'current date is correct'); + + triggerKeydown($viewsWrapper, UP_ARROW_KEY_CODE, { ctrlKey: true }); + this.clock.tick(VIEW_ANIMATION_DURATION); + + triggerKeydown($viewsWrapper, LEFT_ARROW_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), new Date(1940, 0, 6), 'current date is correct'); + }); + + QUnit.test('up/down arrows should try focus the month moved by offset in a year view', function(assert) { + const calendar = this.calendar; + + calendar.option({ + value: new Date(2020, 4, 6), + zoomLevel: 'year', + disabledDates: (args) => { + const month = args.date.getMonth(); + return month < 4 || month > 7; + } + }); + const $viewsWrapper = $(calendar._$viewsWrapper); + + calendar.focus(); + + triggerKeydown($viewsWrapper, UP_ARROW_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), new Date(2019, 4, 6), 'closest month by offset has been focused'); + + triggerKeydown($viewsWrapper, DOWN_ARROW_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), new Date(2020, 4, 6), 'closest month by offset has been focused'); + }); + + QUnit.test('zoomLevel option change should focus the closest available date', function(assert) { + const calendar = this.calendar; + + calendar.option({ + value: new Date(2020, 0, 6), + zoomLevel: 'year', + disabledDates: (args) => { + const year = args.date.getYear(); + const date = args.date.getDate(); + + if(args.view === 'decade') { + return year === 120; + } + + return date === 6; + } + }); + const $viewsWrapper = $(calendar._$viewsWrapper); + + calendar.focus(); + + triggerKeydown($viewsWrapper, DOWN_ARROW_KEY_CODE, { ctrlKey: true }); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 7), 'closest date has been focused'); + + triggerKeydown($viewsWrapper, UP_ARROW_KEY_CODE, { ctrlKey: true }); + this.clock.tick(VIEW_ANIMATION_DURATION); + + triggerKeydown($viewsWrapper, UP_ARROW_KEY_CODE, { ctrlKey: true }); + this.clock.tick(VIEW_ANIMATION_DURATION); + + assert.deepEqual(calendar.option('currentDate'), new Date(2021, 0, 7), 'closest date has been focused'); + }); + + QUnit.test('zoomLevel option change should contour the current view even if current date has not been changed', function(assert) { + const currentDate = new Date(2020, 0, 6); + const calendar = this.calendar; + const $viewsWrapper = $(calendar._$viewsWrapper); + + calendar.option({ + value: currentDate, + }); + + calendar.focus(); + + triggerKeydown($viewsWrapper, UP_ARROW_KEY_CODE, { ctrlKey: true }); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), currentDate, 'currentDate has not been changed'); + assert.deepEqual(calendar._view.option('contouredDate'), currentDate, 'contoured date is correct'); + }); + + QUnit.test('left/right/up/downArrow should work like pageUp/Down when navigating to the disabled month', function(assert) { + const isAnimationOff = fx.off; + const animationSpy = sinon.spy(fx, 'animate'); + + try { + const currentDateOnJanuary = new Date(2020, 0, 15); + const currentDateOnFebruary = new Date(2020, 1, 15); + const currentDateOnMarch = new Date(2020, 2, 15); + const currentDateOnApril = new Date(2020, 3, 15); + const calendar = this.calendar; + + calendar.option({ + value: currentDateOnJanuary, + disabledDates: (args) => { + const month = args.date.getMonth(); + return month === 1 || month === 2; + } + }); + const $viewsWrapper = $(calendar._$viewsWrapper); + + fx.off = false; + animationSpy.resetHistory(); + + calendar.focus(); + + triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE, { ctrlKey: true }); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), currentDateOnFebruary, 'the same date has been focused'); + assert.equal(animationSpy.callCount, 1, 'view has been changed'); + + triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), currentDateOnMarch, 'the same date has been focused'); + assert.equal(animationSpy.callCount, 2, 'view has been changed'); + + triggerKeydown($viewsWrapper, DOWN_ARROW_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), currentDateOnApril, 'the same date has been focused'); + assert.equal(animationSpy.callCount, 3, 'view has been changed'); + + triggerKeydown($viewsWrapper, LEFT_ARROW_KEY_CODE, { ctrlKey: true }); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), currentDateOnMarch, 'the same date has been focused'); + assert.equal(animationSpy.callCount, 4, 'view has been changed'); + + triggerKeydown($viewsWrapper, LEFT_ARROW_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), currentDateOnFebruary, 'the same date has been focused'); + assert.equal(animationSpy.callCount, 5, 'view has been changed'); + + triggerKeydown($viewsWrapper, UP_ARROW_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), currentDateOnJanuary, 'the same date has been focused'); + assert.equal(animationSpy.callCount, 6, 'view has been changed'); + + } finally { + fx.off = isAnimationOff; + animationSpy.restore(); + } + }); + + QUnit.test('calendar should properly set the first and the last available cells', function(assert) { + this.reinit({ + disabledDates: (args) => { + const disabledDays = [1, 2, 28, 30]; + if(disabledDays.indexOf(args.date.getDate()) > -1) { + return true; + } + }, + value: this.value, + focusStateEnabled: true + }); + const calendar = this.calendar; + const $viewsWrapper = $(calendar._$viewsWrapper); + + calendar.focus(); + + triggerKeydown($viewsWrapper, HOME_KEY_CODE); + assert.deepEqual(calendar.option('currentDate'), new Date(2010, 10, 3)); + + triggerKeydown($viewsWrapper, END_KEY_CODE); + assert.deepEqual(calendar.option('currentDate'), new Date(2010, 10, 29)); + }); + + QUnit.test('home/end keys should not do anything if all dates in the current month are disabled', function(assert) { + const calendar = this.calendar; + const $viewsWrapper = $(calendar._$viewsWrapper); + + calendar.option('value', new Date(2010, 11, 10)); + + calendar.focus(); + + triggerKeydown($viewsWrapper, HOME_KEY_CODE); + assert.deepEqual(calendar.option('currentDate'), this.calendar.option('value')); + + triggerKeydown($viewsWrapper, END_KEY_CODE); + assert.deepEqual(calendar.option('currentDate'), this.calendar.option('value')); + }); + + QUnit.test('enter key should not change selected value if focused date is disabled', function(assert) { + const startDate = new Date(2020, 0, 6); + const calendar = this.calendar; + + calendar.option({ + value: startDate, + disabledDates: (args) => { + return args.date.getMonth() === 1; + } + }); + const $viewsWrapper = $(calendar._$viewsWrapper); + + calendar.focus(); + + triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE, { ctrlKey: true }); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), new Date(2020, 1, 6), 'current date is correct'); + + triggerKeydown($viewsWrapper, ENTER_KEY_CODE); + assert.deepEqual(calendar.option('value'), startDate, 'selected value has not been changed'); + }); + + QUnit.test('enter key should change selected value if focused date is not disabled', function(assert) { + const startDate = new Date(2020, 0, 6); + const newDate = new Date(2020, 1, 6); + const calendar = this.calendar; + const $viewsWrapper = $(calendar._$viewsWrapper); + + calendar.option({ + value: startDate, + }); + + calendar.focus(); + + triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE, { ctrlKey: true }); + + assert.deepEqual(calendar.option('currentDate'), newDate, 'current date has been changed'); + assert.deepEqual(calendar.option('value'), startDate, 'selected value is correct'); + + triggerKeydown($viewsWrapper, ENTER_KEY_CODE); + + assert.deepEqual(calendar.option('currentDate'), newDate, 'current date is correct'); + assert.deepEqual(calendar.option('value'), newDate, 'selected value has been changed'); + }); + + QUnit.test('home/end keys should focus the first/last available date in the current month', function(assert) { + const calendar = this.calendar; + + calendar.option({ + value: new Date(2020, 0, 15), + disabledDates: (args) => { + const date = args.date.getDate(); + return date <= 7 || date >= 23; + } + }); + const $viewsWrapper = $(calendar._$viewsWrapper); + + calendar.focus(); + + triggerKeydown($viewsWrapper, HOME_KEY_CODE); + assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 8)); + + triggerKeydown($viewsWrapper, END_KEY_CODE); + assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 22)); + }); + + QUnit.test('the focused date should always be in the [min, max] range', function(assert) { + const isAnimationOff = fx.off; + const animationSpy = sinon.spy(fx, 'animate'); + + try { + const calendar = this.calendar; + + calendar.option({ + value: new Date(2020, 0, 25), + disabledDates: (args) => { + const date = args.date.getDate(); + return date >= 5 && date <= 20; + }, + max: new Date(2020, 1, 20), + min: new Date(2020, 0, 25) + }); + const $viewsWrapper = $(calendar._$viewsWrapper); + + fx.off = false; + animationSpy.resetHistory(); + + calendar.focus(); + + triggerKeydown($viewsWrapper, PAGE_DOWN_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), new Date(2020, 1, 4), 'focused date is in the range (min, max)'); + assert.equal(animationSpy.callCount, 1, 'view has been changed'); + + triggerKeydown($viewsWrapper, PAGE_UP_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 25), 'focused date is in the range (min, max)'); + assert.equal(animationSpy.callCount, 2, 'view has been changed'); + + } finally { + fx.off = isAnimationOff; + animationSpy.restore(); + } + }); + + QUnit.test('up/downArrow should try focus the same date in the next/previous month when the column is disabled', function(assert) { + const isAnimationOff = fx.off; + const animationSpy = sinon.spy(fx, 'animate'); + + try { + const calendar = this.calendar; + + calendar.option({ + value: new Date(2020, 1, 5), + disabledDates: (args) => { + const day = args.date.getDay(); + const month = args.date.getMonth(); + const date = args.date.getDate(); + return month === 0 && day === 3 || month === 1 && day === 0 || month === 0 && day === 0 && date !== 5; + } + }); + const $viewsWrapper = $(calendar._$viewsWrapper); + + fx.off = false; + animationSpy.resetHistory(); + + calendar.focus(); + + triggerKeydown($viewsWrapper, UP_ARROW_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 5), 'the closest available date has been focused'); + assert.equal(animationSpy.callCount, 1, 'view has been changed'); + + triggerKeydown($viewsWrapper, DOWN_ARROW_KEY_CODE); + this.clock.tick(VIEW_ANIMATION_DURATION); + assert.deepEqual(calendar.option('currentDate'), new Date(2020, 1, 5), 'the closest available date has been focused'); + assert.equal(animationSpy.callCount, 2, 'view has been changed'); + + } finally { + fx.off = isAnimationOff; + animationSpy.restore(); + } + }); + + QUnit.test('calendar should properly initialize currentDate when initial value is disabled', function(assert) { + this.reinit({ + disabledDates: (args) => { + if(args.date.valueOf() === new Date(2010, 10, 10).valueOf()) { + return true; + } + }, + value: this.value, + focusStateEnabled: true + }); + + const calendar = this.calendar; + assert.ok(dateUtils.sameView(calendar.option('zoomLevel'), calendar.option('currentDate'), new Date(2010, 10, 11))); + }); + + QUnit.test('arrowUp/Down should focus cell on top/bottom', function(assert) { + const calendar = this.calendar; + + calendar.option({ + disabledDates: (args) => { + return args.date.getDate() === 15 || args.date.getDate() === 11; + }, + value: new Date(2020, 0, 16) + }); + const $viewsWrapper = $(calendar._$viewsWrapper); + + calendar.focus(); + + triggerKeydown($viewsWrapper, UP_ARROW_KEY_CODE); + assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 9), 'cell on top has been focused'); + + triggerKeydown($viewsWrapper, DOWN_ARROW_KEY_CODE); + assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 16), 'cell on bottom has been focused'); + }); + + QUnit.test('value can be changed to disabled date', function(assert) { + const calendar = this.calendar; + const disabledDate = new Date(2010, 9, 10); + + calendar.option('value', disabledDate); + assert.strictEqual(calendar.option('value'), disabledDate, 'value is changed'); + }); + + QUnit.test('disabledDates argument contains correct component parameter', function(assert) { + const stub = sinon.stub(); + + this.reinit({ + disabledDates: stub, + value: this.value, + focusStateEnabled: true + }); + + const component = stub.lastCall.args[0].component; + assert.equal(component.NAME, 'dxCalendar', 'Correct component'); + }); + + QUnit.test('current day should be the same as selected on init when current month is disabled', function(assert) { + this.calendar.option('value', new Date(2010, 11, 10)); + + assert.deepEqual(this.calendar.option('currentDate'), this.calendar.option('value'), 'currentDate is the same as selected date'); + }); + + QUnit.test('current day should be set to the closest available date on init when there is available date on the current month', function(assert) { + this.calendar.option({ + value: new Date(2010, 10, 14), + disabledDates: (args) => { + return args.date.getDate() > 10 && args.date.getDate() <= 20; + } + }); + + assert.deepEqual(this.calendar.option('currentDate'), new Date(2010, 10, 10), 'currentDate is the closest available date'); + }); + + QUnit.test('It should not be possible to focus dates that are disabled using combination of disabledDates+min/max', function(assert) { + const calendar = this.calendar; + + calendar.option({ + value: new Date('2023/09/11'), + max: new Date('2023/09/16'), + min: new Date('2023/09/10'), + disabledDates: (d) => { + const day = d.date.getDay(); + + return d.view === 'month' && day === 0 || day === 6; + }, + }); + const $viewsWrapper = $(calendar._$viewsWrapper); + + calendar.focus(); + + triggerKeydown($viewsWrapper, LEFT_ARROW_KEY_CODE); + assert.deepEqual(calendar.option('currentDate'), new Date('2023/09/11'), 'left disabledDate is not focused'); + + calendar.option('value', new Date('2023/09/15')); + + triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE); + assert.deepEqual(calendar.option('currentDate'), new Date('2023/09/15'), 'right disabledDate is not focused'); + }); +}); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/calendar.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/calendar.tests.js index da42eed663b1..077af5d3158a 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/calendar.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/calendar.tests.js @@ -3,19 +3,17 @@ import { camelize } from 'core/utils/inflector'; import translator from 'common/core/animation/translator'; import dateUtils from 'core/utils/date'; import dateSerialization from 'core/utils/date_serialization'; -import { noop } from 'core/utils/common'; import swipeEvents from 'common/core/events/swipe'; import fx from 'common/core/animation/fx'; import Views from '__internal/ui/calendar/calendar.views'; -import Calendar from 'ui/calendar'; import pointerMock from '../../helpers/pointerMock.js'; import keyboardMock from '../../helpers/keyboardMock.js'; import config from 'core/config'; import dataUtils from 'core/element_data'; -import dateLocalization from 'common/core/localization/date'; import { normalizeKeyName } from 'common/core/events/utils/index'; import localization from 'localization'; +import 'ui/calendar'; import 'fluent_blue_light.css!'; // calendar @@ -33,12 +31,6 @@ const CALENDAR_VIEWS_WRAPPER_CLASS = 'dx-calendar-views-wrapper'; // calendar view const CALENDAR_SELECTED_DATE_CLASS = 'dx-calendar-selected-date'; -const CALENDAR_CELL_IN_RANGE_CLASS = 'dx-calendar-cell-in-range'; -const CALENDAR_CELL_RANGE_HOVER_CLASS = 'dx-calendar-cell-range-hover'; -const CALENDAR_CELL_RANGE_HOVER_START_CLASS = 'dx-calendar-cell-range-hover-start'; -const CALENDAR_CELL_RANGE_HOVER_END_CLASS = 'dx-calendar-cell-range-hover-end'; -const CALENDAR_RANGE_START_DATE_CLASS = 'dx-calendar-range-start-date'; -const CALENDAR_RANGE_END_DATE_CLASS = 'dx-calendar-range-end-date'; const CALENDAR_CONTOURED_DATE_CLASS = 'dx-calendar-contoured-date'; const CALENDAR_DATE_VALUE_KEY = 'dxDateValueKey'; @@ -1677,2521 +1669,6 @@ QUnit.module('Calendar footer', { }); -QUnit.module('Options', { - beforeEach: function() { - fx.off = true; - - this.$element = $('
').appendTo('#qunit-fixture'); - this.calendar = this.$element.dxCalendar().dxCalendar('instance'); - - this.reinit = (options) => { - this.$element.remove(); - this.$element = $('
').appendTo('#qunit-fixture'); - this.calendar = this.$element.dxCalendar(options).dxCalendar('instance'); - }; - }, - afterEach: function() { - this.$element.remove(); - fx.off = false; - } -}, () => { - QUnit.test('changing the \'value\' option must invoke the \'onValueChanged\' action', function(assert) { - this.reinit({ - onValueChanged: () => { - assert.ok(true); - } - }); - this.calendar.option('value', new Date(2002, 2, 2)); - }); - - QUnit.test('firstDayOfWeek option', function(assert) { - const getFirstWeekDayCell = () => { - return getCurrentViewInstance(this.calendar).$element().find('th').get(0); - }; - - let $firstWeekDayCell = getFirstWeekDayCell(); - assert.strictEqual($firstWeekDayCell.abbr, 'Sunday', 'first day of week is correct'); - - this.calendar.option('firstDayOfWeek', 1); - - $firstWeekDayCell = getFirstWeekDayCell(); - assert.strictEqual($firstWeekDayCell.abbr, 'Monday', 'first day of week is correct after runtime option change'); - - this.calendar.option('firstDayOfWeek', 2); - - $firstWeekDayCell = getFirstWeekDayCell(); - assert.strictEqual($firstWeekDayCell.abbr, 'Tuesday', 'first day of week is correct after runtime option change'); - }); - - [ - { localeID: 'de', expectedFirstDayOfWeek: 'Montag' }, - { localeID: 'en', expectedFirstDayOfWeek: 'Sunday' }, - { localeID: 'ja', expectedFirstDayOfWeek: '日曜日' }, - // eslint-disable-next-line i18n/no-russian-character - { localeID: 'ru', expectedFirstDayOfWeek: 'понедельник' }, - { localeID: 'zh', expectedFirstDayOfWeek: '星期日' }, - { localeID: 'hr', expectedFirstDayOfWeek: 'ponedjeljak' }, - { localeID: 'ar', expectedFirstDayOfWeek: 'السبت' }, - { localeID: 'el', expectedFirstDayOfWeek: 'Δευτέρα' }, - { localeID: 'ca', expectedFirstDayOfWeek: 'dilluns' }, - ].forEach(({ localeID, expectedFirstDayOfWeek }) => { - QUnit.test(`firstDayOfWeek should depend from locale: ${localeID}`, function(assert) { - const getFirstWeekDayCell = () => { - return getCurrentViewInstance(this.calendar).$element().find('th').get(0); - }; - - const currentLocale = localization.locale(); - - try { - localization.locale(localeID); - - this.reinit({}); - - const $firstWeekDayCell = getFirstWeekDayCell(); - assert.strictEqual($firstWeekDayCell.abbr, expectedFirstDayOfWeek, 'first day of week is correct'); - } finally { - localization.locale(currentLocale); - } - }); - }); - - [ - { weekNumberRule: 'auto', firstDayOfWeek: 1, expectedCalls: { firstFourDays: 36, firstDay: 0, fullWeek: 0 } }, - { weekNumberRule: 'auto', firstDayOfWeek: 0, expectedCalls: { firstFourDays: 0, firstDay: 36, fullWeek: 0 } }, - { weekNumberRule: 'auto', firstDayOfWeek: 5, expectedCalls: { firstFourDays: 0, firstDay: 36, fullWeek: 0 } }, - { weekNumberRule: 'firstDay', firstDayOfWeek: 1, expectedCalls: { firstFourDays: 0, firstDay: 36, fullWeek: 0 } }, - { weekNumberRule: 'firstDay', firstDayOfWeek: 0, expectedCalls: { firstFourDays: 0, firstDay: 36, fullWeek: 0 } }, - { weekNumberRule: 'firstDay', firstDayOfWeek: 5, expectedCalls: { firstFourDays: 0, firstDay: 36, fullWeek: 0 } }, - { weekNumberRule: 'firstFourDays', firstDayOfWeek: 1, expectedCalls: { firstFourDays: 36, firstDay: 0, fullWeek: 0 } }, - { weekNumberRule: 'firstFourDays', firstDayOfWeek: 0, expectedCalls: { firstFourDays: 36, firstDay: 0, fullWeek: 0 } }, - { weekNumberRule: 'firstFourDays', firstDayOfWeek: 5, expectedCalls: { firstFourDays: 36, firstDay: 0, fullWeek: 0 } }, - { weekNumberRule: 'fullWeek', firstDayOfWeek: 1, expectedCalls: { firstFourDays: 0, firstDay: 0, fullWeek: 36 } }, - { weekNumberRule: 'fullWeek', firstDayOfWeek: 0, expectedCalls: { firstFourDays: 0, firstDay: 0, fullWeek: 36 } }, - { weekNumberRule: 'fullWeek', firstDayOfWeek: 5, expectedCalls: { firstFourDays: 0, firstDay: 0, fullWeek: 36 } }, - ].forEach(({ weekNumberRule, firstDayOfWeek, expectedCalls }) => { - QUnit.test(`weekNumberRule option: weekNumberRule="${weekNumberRule}", firstDayOfWeek="${firstDayOfWeek}"`, function(assert) { - const dateUtilsCallCountMap = { - firstDay: 0, - firstFourDays: 0, - fullWeek: 0 - }; - const getWeekNumberStub = sinon.stub(dateUtils, 'getWeekNumber').callsFake((date, firstDayOfWeek, rule) => { - dateUtilsCallCountMap[rule]++; - }); - - try { - this.calendar.option({ - firstDayOfWeek, - weekNumberRule, - showWeekNumbers: true, - currentDate: new Date(2020, 0, 1), - }); - - ['firstDay', 'firstFourDays', 'fullWeek'].forEach((rule) => { - assert.strictEqual(dateUtilsCallCountMap[rule], expectedCalls[rule], `getWeekNumber called ${expectedCalls[rule]} times for ${rule} rule`); - }); - } finally { - getWeekNumberStub.restore(); - } - }); - }); - - QUnit.test('dateSerializationFormat option', function(assert) { - this.calendar.option({ - dateSerializationFormat: 'yyyy-MM-dd', - currentDate: new Date(2020, 0, 0) - }); - - const $cell = this.$element.find(`.${CALENDAR_CELL_CLASS}`).eq(4); - $($cell).trigger('dxclick'); - - const selectedFormattedValue = '2019-11-28'; - const value = this.calendar.option('value'); - assert.strictEqual(value, selectedFormattedValue, 'value format is correct after dateSerializationFormat option runtime change'); - }); - - QUnit.test('cellTemplate option', function(assert) { - this.calendar.option({ - cellTemplate: function() { - return 'Custom template'; - }, - currentDate: new Date(2020, 0, 0) - }); - - const $cell = this.$element.find(`.${CALENDAR_CELL_CLASS}`).eq(4); - const cellContent = $cell.text(); - - assert.strictEqual(cellContent, 'Custom template', 'cell content is correct after cellTemplate runtime change'); - }); - - QUnit.test('cellTemplate is rendered fow week cell', function(assert) { - this.calendar.option({ - cellTemplate: function(cellData, cellIndex) { - return cellIndex === -1 ? 'Week cell template' : `${cellData.text}`; - }, - value: new Date(2022, 0, 1), - showWeekNumbers: true - }); - - const $cell = this.$element.find(`.${CALENDAR_WEEK_NUMBER_CELL_CLASS}`).eq(0); - const cellContent = $cell.text(); - - assert.strictEqual(cellContent, 'Week cell template'); - }); - - QUnit.test('showTodayButton option', function(assert) { - const getTodayButton = () => this.$element.find(`.${CALENDAR_TODAY_BUTTON_CLASS}`).get(0); - - this.calendar.option('showTodayButton', true); - - let $todayButton = getTodayButton(); - assert.strictEqual($($todayButton).text(), 'Today', 'todayButton is rendered after showTodayButton runtime change to true'); - - this.calendar.option('showTodayButton', false); - $todayButton = getTodayButton(); - assert.strictEqual($todayButton, undefined, 'todayButton is not rendered after showTodayButton runtime change to false'); - }); - - QUnit.test('todayButtonText option initialize', function(assert) { - const getTodayButton = () => this.$element.find(`.${CALENDAR_TODAY_BUTTON_CLASS}`).get(0); - - this.reinit({ - showTodayButton: true, - todayButtonText: 'Custom text', - }); - - const $todayButton = getTodayButton(); - assert.strictEqual($($todayButton).text(), 'Custom text', 'todayButton text matches the todayButtonText option'); - }); - - QUnit.test('todayButtonText option', function(assert) { - const getTodayButton = () => this.$element.find(`.${CALENDAR_TODAY_BUTTON_CLASS}`).get(0); - - this.calendar.option({ - showTodayButton: true, - todayButtonText: 'Custom text', - }); - - const $todayButton = getTodayButton(); - assert.strictEqual($($todayButton).text(), 'Custom text', 'todayButton text matches the todayButtonText option'); - }); - - QUnit.test('onCellClick option runtime change', function(assert) { - const getCellElement = () => this.$element.find(`.${CALENDAR_CELL_CLASS}`).eq(4); - - const firstClickHandler = sinon.spy(); - const secondClickHandler = sinon.spy(); - - this.calendar.option({ - currentDate: new Date(2010, 10, 10), - focusStateEnabled: true, - onCellClick: firstClickHandler - }); - - $(getCellElement()).trigger('dxclick'); - assert.ok(firstClickHandler.calledOnce, 'firstClickHandler is called once'); - - this.calendar.option('onCellClick', secondClickHandler); - - $(getCellElement()).trigger('dxclick'); - assert.ok(secondClickHandler.calledOnce, 'secondClickHandler is called once after onCellClick runtime option change'); - }); - - QUnit.test('onCellClick option - subscription by "on" method', function(assert) { - const getCellElement = () => this.$element.find(`.${CALENDAR_CELL_CLASS}`).eq(4); - - const clickHandler = sinon.spy(); - - this.calendar.option({ - currentDate: new Date(2010, 10, 10), - focusStateEnabled: true - }); - this.calendar.on('cellClick', clickHandler); - - $(getCellElement()).trigger('dxclick'); - assert.ok(clickHandler.calledOnce, 'cellClick is called'); - - this.calendar.off('cellClick', clickHandler); - - $(getCellElement()).trigger('dxclick'); - assert.ok(clickHandler.calledOnce, 'cellClick is not called second time'); - }); - - QUnit.test('onContouredChanged option runtime change', function(assert) { - const firstHandler = sinon.spy(); - const secondHandler = sinon.spy(); - - this.reinit({ - value: null, - onContouredChanged: firstHandler, - focusStateEnabled: true - }); - - assert.ok(firstHandler.calledOnce, 'first handler has been called'); - - this.calendar.option('onContouredChanged', secondHandler); - this.calendar.focus(); - triggerKeydown($(this.calendar._$viewsWrapper), UP_ARROW_KEY_CODE, { ctrlKey: true }); - - assert.ok(secondHandler.calledOnce, 'second handler has been called'); - }); - - QUnit.test('onContouredChanged option - subscription by "on" method', function(assert) { - const goNextView = () => { - $(this.$element.find(`.${CALENDAR_NAVIGATOR_NEXT_VIEW_CLASS}`)).trigger('dxclick'); - }; - - const handler = sinon.spy(); - this.reinit({ - value: null, - focusStateEnabled: true - }); - - this.calendar.on('contouredChanged', handler); - goNextView(); - assert.ok(handler.calledOnce, 'handler is called'); - - this.calendar.off('contouredChanged', handler); - goNextView(); - assert.ok(handler.calledOnce, 'handler is not called second time'); - }); - - QUnit.test('onCellClick return not \'undefined\' after click on cell', function(assert) { - const clickHandler = sinon.spy(noop); - - this.reinit({ - currentDate: new Date(2010, 10, 10), - focusStateEnabled: true, - onCellClick: clickHandler - }); - - const $cell = this.$element.find(`.${CALENDAR_CELL_CLASS}`).eq(4); - $($cell).trigger('dxclick'); - - assert.ok(clickHandler.calledOnce, 'onCellClick called once'); - - const params = clickHandler.getCall(0).args[0]; - assert.ok(params, 'Event params should be passed'); - assert.ok(params.event, 'Event should be passed'); - assert.ok(params.component, 'Component should be passed'); - assert.ok(params.element, 'Element should be passed'); - }); - - QUnit.test('onCellClick should not be fired when zoomLevel change required (for datebox integration)', function(assert) { - const clickSpy = sinon.spy(); - - this.reinit({ - onCellClick: clickSpy, - zoomLevel: 'year', - maxZoomLevel: 'month' - }); - - const $cell = $(getCurrentViewInstance(this.calendar).$element().find('.' + CALENDAR_CELL_CLASS).eq(3)); - $($cell).trigger('dxclick'); - - assert.equal(clickSpy.callCount, 0, 'onCellClick was not fired'); - }); - - QUnit.test('Calendar should not allow to select date in disabled state changed in runtime (T196663)', function(assert) { - this.reinit({ - value: new Date(2013, 11, 15), - currentDate: new Date(2013, 11, 15) - }); - - this.calendar.option('disabled', true); - $(this.$element.find('[data-value=\'2013/12/11\']')).trigger('dxclick'); - assert.deepEqual(this.calendar.option('value'), new Date(2013, 11, 15)); - }); - - QUnit.test('When initialized without currentDate, calendar must try to infer it from value', function(assert) { - const date = new Date(2014, 11, 11); - - this.reinit({ - value: new Date(date) - }); - - assert.deepEqual(this.calendar.option('currentDate'), date); - }); - - QUnit.test('calendar view should be changed on the \'currentDate\' option change', function(assert) { - const calendar = this.calendar; - const oldDate = getCurrentViewInstance(calendar).option('date'); - - calendar.option('currentDate', new Date(2013, 11, 15)); - assert.notDeepEqual(getCurrentViewInstance(calendar).option('date'), oldDate, 'view is changed'); - }); - - QUnit.test('contoured date displaying should depend on \'skipFocusCheck\' option', function(assert) { - this.reinit({ - value: new Date(2015, 10, 18), - skipFocusCheck: true - }); - - assert.deepEqual(getCurrentViewInstance(this.calendar).option('contouredDate'), new Date(2015, 10, 18), 'view contoured is set'); - }); - - QUnit.test('_todayDate option should be passed to calendar view', function(assert) { - const calendarTodayDate = () => new Date(2021, 1, 1); - - this.reinit({ _todayDate: calendarTodayDate }); - assert.strictEqual(getCurrentViewInstance(this.calendar).option('_todayDate'), calendarTodayDate, '_todayDate is passed to calendar view'); - }); - - QUnit.test('_todayDate option should be passed to calendar view after runtime option change', function(assert) { - const calendarTodayDate = () => new Date(2021, 1, 1); - - this.calendar.option({ _todayDate: calendarTodayDate }); - assert.strictEqual(getCurrentViewInstance(this.calendar).option('_todayDate'), calendarTodayDate, '_todayDate is passed to calendar view'); - }); - - QUnit.test('_todayDate should return new Date() if it is not specified', function(assert) { - const today = new Date(); - const result = this.calendar.option('_todayDate')(); - - today.setHours(0, 0, 0, 0); - result.setHours(0, 0, 0, 0); - - assert.deepEqual(today, result, 'today date is correct'); - }); - - QUnit.module('SelectionMode', { - beforeEach: function() { - this.options = { - value: [new Date('01/15/2023'), new Date('02/01/2023'), new Date('02/05/2023')], - }; - } - }, () => { - ['multiple', 'range'].forEach((selectionMode) => { - QUnit.test(`Date from value option is not selected when selectionMode is ${selectionMode}`, function(assert) { - this.reinit({ - ...this.options, - selectionMode - }); - const $cell = this.$element.find('*[data-value="2023/01/07"]'); - - assert.notOk($cell.hasClass(CALENDAR_SELECTED_DATE_CLASS)); - }); - - [ - { - value: [new Date('01/05/2023'), new Date('02/01/2023')], - type: 'dates' - }, - { - value: ['01/05/2023', '02/01/2023'], - type: 'strings' - }, - { - value: [1672916400000, 1675249200000], - type: 'numbers' - } - ].forEach(({ value, type }) => { - QUnit.test(`Two dates are selected when selectionMode = ${selectionMode} and value are defined as ${type}`, function(assert) { - this.reinit({ - selectionMode, - value - }); - const $cells = $(getCurrentViewInstance(this.calendar).$element().find(`.${CALENDAR_SELECTED_DATE_CLASS}`)); - - assert.strictEqual($($cells[0]).data('value'), '2023/01/05'); - assert.strictEqual($($cells[1]).data('value'), '2023/02/01'); - }); - }); - - QUnit.module('CurrentDate', {}, () => { - QUnit.test(`Should be equal to the lowest defined date in value on init (selectionMode=${selectionMode}`, function(assert) { - this.reinit({ - value: [null, new Date('01/15/2023'), new Date('02/01/2023')], - selectionMode - }); - const { currentDate, value } = this.calendar.option(); - - assert.deepEqual(currentDate, new Date(Math.min(...value.filter(value => value)))); - }); - - QUnit.test(`Should be equal to the lowest date in value on runtime value change (selectionMode=${selectionMode}`, function(assert) { - this.reinit({ selectionMode }); - this.calendar.option('value', [new Date(), new Date('2020/02/02')]); - const { currentDate, value } = this.calendar.option(); - - assert.deepEqual(currentDate, value[1]); - }); - - QUnit.test(`Should be equal to new selected cell date when selectionMode = ${selectionMode}`, function(assert) { - this.reinit({ - ...this.options, - selectionMode - }); - const $cell = this.$element.find('*[data-value="2023/01/16"]'); - - $cell.trigger('dxclick'); - - const currentDate = this.calendar.option('currentDate'); - - assert.deepEqual(currentDate, new Date('2023/01/16')); - }); - - QUnit.test('Should be equal to deselected cell date when selectionMode = multiple', function(assert) { - this.reinit({ - ...this.options, - selectionMode: 'multiple' - }); - const $cell = this.$element.find('*[data-value="2023/01/15"]'); - - $cell.trigger('dxclick'); - - const currentDate = this.calendar.option('currentDate'); - - assert.deepEqual(currentDate, new Date('2023/01/15')); - }); - }); - }); - - QUnit.module('Multiple', { - beforeEach: function() { - this.reinit({ - ...this.options, - selectionMode: 'multiple' - }); - } - }, () => { - QUnit.test('It should be possible to select another value by click', function(assert) { - const $cell = this.$element.find('*[data-value="2023/01/16"]'); - - $cell.trigger('dxclick'); - - assert.strictEqual(this.calendar.option('value').length, 4); - assert.ok($cell.hasClass(CALENDAR_SELECTED_DATE_CLASS)); - }); - - QUnit.test('It should be possible to deselect already selected value by click', function(assert) { - const $cell = $(getCurrentViewInstance(this.calendar).$element().find('*[data-value="2023/01/15"]')); - - $cell.trigger('dxclick'); - - assert.strictEqual(this.calendar.option('value').length, 2); - assert.notOk($cell.hasClass(CALENDAR_SELECTED_DATE_CLASS)); - }); - }); - - QUnit.module('Range', { - beforeEach: function() { - this.reinit({ - value: ['2023/01/13', '2023/01/17', '2023/01/20'], - selectionMode: 'range' - }); - } - }, () => { - QUnit.test('Only first two dates from value option should be selected', function(assert) { - const $cell1 = this.$element.find('*[data-value="2023/01/13"]'); - const $cell2 = this.$element.find('*[data-value="2023/01/17"]'); - const $cell3 = this.$element.find('*[data-value="2023/01/20"]'); - - assert.ok($cell1.hasClass(CALENDAR_SELECTED_DATE_CLASS)); - assert.ok($cell2.hasClass(CALENDAR_SELECTED_DATE_CLASS)); - assert.notOk($cell3.hasClass(CALENDAR_SELECTED_DATE_CLASS)); - }); - - QUnit.test(`Start value cell should have ${CALENDAR_RANGE_START_DATE_CLASS} class`, function(assert) { - const $cell = $(getCurrentViewInstance(this.calendar).$element().find('*[data-value="2023/01/13"]')); - - assert.ok($cell.hasClass(CALENDAR_RANGE_START_DATE_CLASS)); - }); - - QUnit.test(`End value cell should have ${CALENDAR_RANGE_END_DATE_CLASS} class`, function(assert) { - const $cell = $(getCurrentViewInstance(this.calendar).$element().find('*[data-value="2023/01/17"]')); - - assert.ok($cell.hasClass(CALENDAR_RANGE_END_DATE_CLASS)); - }); - - QUnit.test(`Cells between startDate and endDate should have ${CALENDAR_CELL_IN_RANGE_CLASS} class`, function(assert) { - const $cell = $(getCurrentViewInstance(this.calendar).$element().find('*[data-value="2023/01/15"]')); - - assert.ok($cell.hasClass(CALENDAR_CELL_IN_RANGE_CLASS)); - }); - - - QUnit.test(`Cells between startDate and endDate should have ${CALENDAR_CELL_IN_RANGE_CLASS} class even after currentDate runtime change (T1253076)`, function(assert) { - this.reinit({ - value: ['2025/01/01', '2025/12/31'], - selectionMode: 'range', - viewsCount: 2, - }); - - this.calendar.option('currentDate', new Date('2025-12-31')); - - const $prevButton = $(this.$element.find(`.${CALENDAR_NAVIGATOR_PREVIOUS_VIEW_CLASS}`)); - $prevButton.trigger('dxclick'); - - const $cell = $(getCurrentViewInstance(this.calendar).$element().find('*[data-value="2025/11/01"]')); - - assert.ok($cell.hasClass(CALENDAR_CELL_IN_RANGE_CLASS), 'cell is highlighted'); - }); - - QUnit.test('Should reselect startDate and clear endDate on click when both value are defined', function(assert) { - const expectedValue = [new Date('2023/01/11'), null]; - const $cell = $(getCurrentViewInstance(this.calendar).$element().find('*[data-value="2023/01/11"]')); - - $cell.trigger('dxclick'); - - assert.deepEqual(this.calendar.option('value'), expectedValue); - }); - - QUnit.test('Should select endDate on cell click when startDate is alredy defined and endDate not', function(assert) { - this.reinit({ - value: ['2023/01/13', null], - selectionMode: 'range' - }); - const expectedValue = [new Date('2023/01/13'), new Date('2023/01/15')]; - const $cell = $(getCurrentViewInstance(this.calendar).$element().find('*[data-value="2023/01/15"]')); - - $cell.trigger('dxclick'); - - assert.deepEqual(this.calendar.option('value'), expectedValue); - }); - - QUnit.test('Should swap startDate and endDate on cell when clicked endDate is less then startDate', function(assert) { - this.reinit({ - value: ['2023/01/13', null], - selectionMode: 'range' - }); - const expectedValue = [new Date('2023/01/07'), new Date('2023/01/13')]; - const $cell = $(getCurrentViewInstance(this.calendar).$element().find('*[data-value="2023/01/07"]')); - - $cell.trigger('dxclick'); - - assert.deepEqual(this.calendar.option('value'), expectedValue); - }); - - [ - { - value: [null, null], - scenario: 'when both values are not defined' - }, - { - value: ['2023/01/13', '2023/01/17'], - scenario: 'when both values are defined' - } - ].forEach(({ value, scenario }) => { - QUnit.test(`Cells should not have ${CALENDAR_CELL_IN_RANGE_CLASS} class on hover ${scenario}`, function(assert) { - this.reinit({ - value, - selectionMode: 'range' - }); - const $cell = $(getCurrentViewInstance(this.calendar).$element().find('*[data-value="2023/01/25"]')); - - $cell.trigger('mouseenter'); - - assert.notOk($cell.hasClass(CALENDAR_CELL_IN_RANGE_CLASS)); - }); - }); - - QUnit.test(`Cells should have ${CALENDAR_CELL_RANGE_HOVER_CLASS} class on hover when only startDate is defined`, function(assert) { - this.reinit({ - value: ['2023/01/13', null], - selectionMode: 'range' - }); - - const getCell = (date) => { - return $(getCurrentViewInstance(this.calendar).$element().find(`*[data-value="${date}"]`)); - }; - - getCell('2023/01/15').trigger('mouseenter'); - - const hoveredRange = getCurrentViewInstance(this.calendar).option('hoveredRange'); - - assert.strictEqual(hoveredRange.length, 3, 'hovered range is correct'); - - assert.strictEqual(getCell('2023/01/15').hasClass(CALENDAR_CELL_RANGE_HOVER_CLASS), true, `${CALENDAR_CELL_RANGE_HOVER_CLASS} class`); - assert.strictEqual(getCell('2023/01/15').hasClass(CALENDAR_CELL_RANGE_HOVER_END_CLASS), true, `${CALENDAR_CELL_RANGE_HOVER_END_CLASS} class`); - assert.strictEqual(getCell('2023/01/15').hasClass(CALENDAR_CELL_RANGE_HOVER_START_CLASS), false, `${CALENDAR_CELL_RANGE_HOVER_START_CLASS} class`); - - assert.strictEqual(getCell('2023/01/14').hasClass(CALENDAR_CELL_RANGE_HOVER_CLASS), true, `${CALENDAR_CELL_RANGE_HOVER_CLASS} class`); - assert.strictEqual(getCell('2023/01/14').hasClass(CALENDAR_CELL_RANGE_HOVER_END_CLASS), false, `${CALENDAR_CELL_RANGE_HOVER_END_CLASS} class`); - assert.strictEqual(getCell('2023/01/14').hasClass(CALENDAR_CELL_RANGE_HOVER_START_CLASS), false, `${CALENDAR_CELL_RANGE_HOVER_START_CLASS} class`); - - assert.strictEqual(getCell('2023/01/13').hasClass(CALENDAR_CELL_RANGE_HOVER_CLASS), true, `${CALENDAR_CELL_RANGE_HOVER_CLASS} class`); - assert.strictEqual(getCell('2023/01/13').hasClass(CALENDAR_CELL_RANGE_HOVER_END_CLASS), false, `${CALENDAR_CELL_RANGE_HOVER_END_CLASS} class`); - assert.strictEqual(getCell('2023/01/13').hasClass(CALENDAR_CELL_RANGE_HOVER_START_CLASS), true, `${CALENDAR_CELL_RANGE_HOVER_START_CLASS} class`); - }); - - QUnit.test('Hovered range should be cleared after mouseleave on viewsWrapper element', function(assert) { - this.reinit({ - value: ['2023/01/13', null], - selectionMode: 'range' - }); - const $cell = $(getCurrentViewInstance(this.calendar).$element().find('*[data-value="2023/01/15"]')); - - $cell.trigger('mouseenter'); - - assert.strictEqual(getCurrentViewInstance(this.calendar).option('hoveredRange').length, 3, 'hovered range is correct'); - - const $viewsWrapper = $(this.$element.find(`.${CALENDAR_VIEWS_WRAPPER_CLASS}`)); - - $viewsWrapper.trigger('mouseleave'); - - assert.strictEqual(getCurrentViewInstance(this.calendar).option('hoveredRange').length, 0, 'hovered range is cleared'); - }); - - QUnit.test('Selected range should be reduced when difference between startDate and endDate is bigger than four mounths', function(assert) { - this.reinit({ - value: ['1996/01/05', '2121/03/07'], - selectionMode: 'range', - }); - - const selectedRange = getCurrentViewInstance(this.calendar).option('range'); - - assert.ok(selectedRange.length < 240); - }); - - [1, 2].forEach((viewsCount) => { - QUnit.test(`Big range should start from first date of before view and end on last date of after view (viewsCount=${viewsCount})`, function(assert) { - this.reinit({ - value: ['1996/01/05', '2345/03/07'], - selectionMode: 'range', - viewsCount, - }); - - this.calendar.option('currentDate', new Date('2023/07/24')); - - const expectedRangeStart = new Date('2023/06/01'); - const expectedRangeEnd = viewsCount === 1 ? new Date('2023/08/31') : new Date('2023/09/30'); - const selectedRange = getCurrentViewInstance(this.calendar).option('range'); - const rangeStart = selectedRange[0]; - const rangeEnd = selectedRange[selectedRange.length - 1]; - - assert.deepEqual(rangeStart, expectedRangeStart, 'range start date is first date in views'); - assert.deepEqual(rangeEnd, expectedRangeEnd, 'range end date is last date in views'); - }); - - QUnit.test(`Big range should start from start date if start date is date in before view (viewsCount=${viewsCount})`, function(assert) { - this.reinit({ - value: ['1996/01/05', '2345/03/07'], - selectionMode: 'range', - viewsCount, - }); - - this.calendar.option('currentDate', new Date('2023/07/24')); - this.calendar.option('currentDate', new Date('1996/02/15')); - - const expectedRangeStart = new Date('1996/01/05'); - const expectedRangeEnd = viewsCount === 1 ? new Date('1996/03/31') : new Date('1996/04/30'); - const selectedRange = getCurrentViewInstance(this.calendar).option('range'); - const rangeStart = selectedRange[0]; - const rangeEnd = selectedRange[selectedRange.length - 1]; - - assert.deepEqual(rangeStart, expectedRangeStart, 'range start date is start date'); - assert.deepEqual(rangeEnd, expectedRangeEnd, 'range end date is last date in views'); - }); - - QUnit.test(`Big range should end on end date if end date is date from views (viewsCount=${viewsCount})`, function(assert) { - this.reinit({ - value: ['1996/01/05', '2345/03/07'], - selectionMode: 'range', - viewsCount, - }); - - this.calendar.option('currentDate', new Date('2345/03/15')); - - const expectedRangeStart = new Date('2345/02/01'); - const expectedRangeEnd = new Date('2345/03/07'); - const selectedRange = getCurrentViewInstance(this.calendar).option('range'); - const rangeStart = selectedRange[0]; - const rangeEnd = selectedRange[selectedRange.length - 1]; - - assert.deepEqual(rangeStart, expectedRangeStart, 'range start date is first date in views'); - assert.deepEqual(rangeEnd, expectedRangeEnd, 'range end date is end date'); - }); - }); - - [ - [null, null], - [new Date(2021, 9, 17), null], - [null, new Date(2021, 10, 25)], - [new Date(2021, 9, 10), new Date(2021, 9, 17)] - ].forEach((value) => { - QUnit.test(`Click by cell should change startDate value if allowChangeSelectionOrder is true and currentSelection is startDate, initial value: ${JSON.stringify(value)}`, function(assert) { - this.reinit({ - value, - selectionMode: 'range', - allowChangeSelectionOrder: true, - currentSelection: 'startDate', - }); - - let $startDateCell = $(this.calendar.$element()).find(`.${CALENDAR_CELL_CLASS}`).eq(20); - let startCellDate = dataUtils.data($startDateCell.get(0), CALENDAR_DATE_VALUE_KEY); - $startDateCell.trigger('dxclick'); - - assert.deepEqual(this.calendar.option('value'), [startCellDate, value[1]]); - - $startDateCell = $(this.calendar.$element()).find(`.${CALENDAR_CELL_CLASS}`).eq(15); - startCellDate = dataUtils.data($startDateCell.get(0), CALENDAR_DATE_VALUE_KEY); - $startDateCell.trigger('dxclick'); - - assert.deepEqual(this.calendar.option('value'), [startCellDate, value[1]]); - }); - - QUnit.test(`Click by cell should change startDate value and reselect endDate if allowChangeSelectionOrder is true and currentSelection is startDate, startDate > endDate, initial value: ${JSON.stringify(value)}`, function(assert) { - this.reinit({ - value, - selectionMode: 'range', - allowChangeSelectionOrder: true, - currentSelection: 'startDate', - }); - - const $startDateCell = $(this.calendar.$element()).find(`.${CALENDAR_CELL_CLASS}`).eq(30); - const startCellDate = dataUtils.data($startDateCell.get(0), CALENDAR_DATE_VALUE_KEY); - $startDateCell.trigger('dxclick'); - - assert.deepEqual(this.calendar.option('value'), [startCellDate, null]); - }); - - QUnit.test(`Click by cell should change endDate value and reselect startDate if allowChangeSelectionOrder is true and currentSelection is endDate, endDate < startDate, initial value: ${JSON.stringify(value)}`, function(assert) { - this.reinit({ - value, - selectionMode: 'range', - allowChangeSelectionOrder: true, - currentSelection: 'endDate', - }); - - const $endCellDate = $(this.calendar.$element()).find(`.${CALENDAR_CELL_CLASS}`).eq(7); - const endCellDate = dataUtils.data($endCellDate.get(0), CALENDAR_DATE_VALUE_KEY); - $endCellDate.trigger('dxclick'); - - assert.deepEqual(this.calendar.option('value'), endCellDate < value[0] ? [endCellDate, null] : [null, endCellDate]); - }); - - QUnit.test(`Click by cell should change endDate value if allowChangeSelectionOrder is true and currentSelection is endDate, initial value: ${JSON.stringify(value)}`, function(assert) { - this.reinit({ - value, - selectionMode: 'range', - allowChangeSelectionOrder: true, - currentSelection: 'endDate', - }); - - let $endDateCell = $(this.calendar.$element()).find(`.${CALENDAR_CELL_CLASS}`).eq(25); - let endCellDate = dataUtils.data($endDateCell.get(0), CALENDAR_DATE_VALUE_KEY); - $endDateCell.trigger('dxclick'); - - assert.deepEqual(this.calendar.option('value'), [value[0], endCellDate]); - - $endDateCell = $(this.calendar.$element()).find(`.${CALENDAR_CELL_CLASS}`).eq(30); - endCellDate = dataUtils.data($endDateCell.get(0), CALENDAR_DATE_VALUE_KEY); - $endDateCell.trigger('dxclick'); - - assert.deepEqual(this.calendar.option('value'), [value[0], endCellDate]); - }); - - QUnit.test(`Click by cell should change endDate then startDate value if allowChangeSelectionOrder is true and currentSelection is endDate then startDate, initial value: ${JSON.stringify(value)}`, function(assert) { - this.reinit({ - value, - selectionMode: 'range', - allowChangeSelectionOrder: true, - currentSelection: 'endDate', - }); - - const $endDateCell = $(this.calendar.$element()).find(`.${CALENDAR_CELL_CLASS}`).eq(30); - const endCellDate = dataUtils.data($endDateCell.get(0), CALENDAR_DATE_VALUE_KEY); - $endDateCell.trigger('dxclick'); - - assert.deepEqual(this.calendar.option('value'), [value[0], endCellDate]); - - this.calendar.option('currentSelection', 'startDate'); - - const $startDateCell = $(this.calendar.$element()).find(`.${CALENDAR_CELL_CLASS}`).eq(10); - const startCellDate = dataUtils.data($startDateCell.get(0), CALENDAR_DATE_VALUE_KEY); - $startDateCell.trigger('dxclick'); - - assert.deepEqual(this.calendar.option('value'), [startCellDate, endCellDate]); - }); - }); - - QUnit.test('Range should not be displayed on cell hover if only startDate is defined and allowChangeSelectionOrder is true and currentSelection is startDate', function(assert) { - this.reinit({ - value: ['2023/04/01', null], - selectionMode: 'range', - allowChangeSelectionOrder: true, - currentSelection: 'startDate', - }); - - const $cellToHover = $(this.calendar.$element()).find(`.${CALENDAR_CELL_CLASS}`).eq(20); - - $cellToHover.trigger('mouseenter'); - - assert.notOk($cellToHover.hasClass(CALENDAR_CELL_IN_RANGE_CLASS)); - }); - }); - - [ - { - initialSelectionMode: 'multiple', - newSelectionMode: 'single', - optionName: 'value', - expectedValue: null, - }, - { - initialSelectionMode: 'single', - newSelectionMode: 'range', - optionName: 'value', - expectedValue: [null, null], - }, - { - initialSelectionMode: 'range', - newSelectionMode: 'multiple', - optionName: 'value', - expectedValue: [], - }, - ].forEach(({ initialSelectionMode, newSelectionMode, optionName, expectedValue }) => { - QUnit.test(`Value should be restored after switching from ${initialSelectionMode} to ${newSelectionMode} selectionMode`, function(assert) { - const value = initialSelectionMode === 'single' ? new Date() : this.options.value; - this.reinit({ - value, - selectionMode: initialSelectionMode, - }); - - this.calendar.option('selectionMode', newSelectionMode); - - assert.deepEqual(this.calendar.option(optionName), expectedValue); - }); - - QUnit.test(`No cells should be selected after switching from ${initialSelectionMode} to ${newSelectionMode} selectionMode`, function(assert) { - const value = initialSelectionMode === 'single' ? new Date() : this.options.value; - this.reinit({ - value, - selectionMode: initialSelectionMode, - }); - - this.calendar.option('selectionMode', newSelectionMode); - - const $cells = $(getCurrentViewInstance(this.calendar).$element().find(`.${CALENDAR_SELECTED_DATE_CLASS}`)); - - assert.strictEqual($cells.length, 0); - }); - }); - - QUnit.module('SelectWeekOnClick', { - beforeEach: function() { - this.initialValue = ['2023/08/08', '2023/08/16', '2023/08/20']; - } - }, () => { - ['multiple', 'range'].forEach((selectionMode) => { - ['init', 'runtime'].forEach((scenario) => { - QUnit.test(`Click on week number should select week (selectionMode=${selectionMode};selectWeekOnClick=true on ${scenario})`, function(assert) { - this.reinit({ - value: this.initialValue, - selectionMode, - selectWeekOnClick: scenario === 'init', - showWeekNumbers: true, - }); - - if(scenario === 'runtime') { - this.calendar.option('selectWeekOnClick', true); - } - - const $row = this.$element.find('tr').eq(3); - const $weekNumberCell = $row.find(`.${CALENDAR_WEEK_NUMBER_CELL_CLASS}`); - const firstDateInRow = dataUtils.data($row.find(`.${CALENDAR_CELL_CLASS}`).first().get(0), CALENDAR_DATE_VALUE_KEY); - const lastDateInRow = dataUtils.data($row.find(`.${CALENDAR_CELL_CLASS}`).last().get(0), CALENDAR_DATE_VALUE_KEY); - - $weekNumberCell.trigger('dxclick'); - - const value = this.calendar.option('value'); - const expectedValueLength = selectionMode === 'multiple' ? 7 : 2; - - assert.strictEqual(value.length, expectedValueLength, `${value.length} days are selected`); - assert.deepEqual(value[0], firstDateInRow, 'fisrt selected date is first date in row'); - assert.deepEqual(value[value.length - 1], lastDateInRow, 'last selected date is last date in row'); - }); - - QUnit.test(`Click on week number should not select week (selectionMode=${selectionMode};selectWeekOnClick=false on ${scenario})`, function(assert) { - this.reinit({ - value: this.initialValue, - selectionMode, - selectWeekOnClick: scenario !== 'init', - showWeekNumbers: true, - }); - - if(scenario === 'runtime') { - this.calendar.option('selectWeekOnClick', false); - } - - const $row = this.$element.find('tr').eq(3); - const $weekNumberCell = $row.find(`.${CALENDAR_WEEK_NUMBER_CELL_CLASS}`); - - $weekNumberCell.trigger('dxclick'); - - const value = this.calendar.option('value'); - - assert.deepEqual(value, this.initialValue, 'values are not changed'); - }); - }); - - QUnit.test(`Click on week number should select nothing when all dates are disabled (selectionMode=${selectionMode})`, function(assert) { - this.reinit({ - selectionMode, - showWeekNumbers: true, - disabledDates: () => true, - }); - - const $row = this.$element.find('tr').eq(3); - const $weekNumberCell = $row.find(`.${CALENDAR_WEEK_NUMBER_CELL_CLASS}`); - - $weekNumberCell.trigger('dxclick'); - - const value = this.calendar.option('value'); - const expectedValue = selectionMode === 'range' ? [null, null] : []; - - assert.deepEqual(value, expectedValue, 'no dates are selected'); - }); - - QUnit.test(`Click on week number should not select dates that are less than min/bigger than max (selectionMode=${selectionMode})`, function(assert) { - const date = new Date('2023/09/05'); - this.reinit({ - selectionMode, - showWeekNumbers: true, - currentDate: date, - min: date, - max: date, - }); - - const $row = this.$element.find('tr').eq(2); - const $weekNumberCell = $row.find(`.${CALENDAR_WEEK_NUMBER_CELL_CLASS}`); - - $weekNumberCell.trigger('dxclick'); - - const value = this.calendar.option('value'); - const expectedValue = selectionMode === 'multiple' ? [date] : [date, date]; - - assert.deepEqual(value, expectedValue); - }); - }); - - QUnit.test('Click on week number should not select disabled dates in multiple selectionMode', function(assert) { - this.reinit({ - selectionMode: 'multiple', - showWeekNumbers: true, - disabledDates: ({ date }) => date.getDay() !== 0, - }); - - const $row = this.$element.find('tr').eq(3); - const $weekNumberCell = $row.find(`.${CALENDAR_WEEK_NUMBER_CELL_CLASS}`); - - $weekNumberCell.trigger('dxclick'); - - const value = this.calendar.option('value'); - - assert.strictEqual(value.length, 1, 'only one day is selected'); - }); - - QUnit.test('Click on week number should select dates correctly when min/max=null (selectionMode=multiple)', function(assert) { - this.reinit({ - selectionMode: 'multiple', - showWeekNumbers: true, - min: null, - max: null, - }); - - const $row = this.$element.find('tr').eq(2); - const $weekNumberCell = $row.find(`.${CALENDAR_WEEK_NUMBER_CELL_CLASS}`); - - $weekNumberCell.trigger('dxclick'); - - const valueLength = this.calendar.option('value').length; - - assert.deepEqual(valueLength, 7, 'week is selected'); - }); - - QUnit.test('Click on week number should select range from first available date to last available date', function(assert) { - this.reinit({ - selectionMode: 'range', - showWeekNumbers: true, - firstDayOfWeek: 0, - disabledDates: ({ date }) => { - const day = date.getDay(); - return day === 0 || day === 6 || day === 3; - } - }); - - const $row = this.$element.find('tr').eq(3); - const $weekNumberCell = $row.find(`.${CALENDAR_WEEK_NUMBER_CELL_CLASS}`); - const firstDateInRow = dataUtils.data($row.find(`.${CALENDAR_CELL_CLASS}`).first().get(0), CALENDAR_DATE_VALUE_KEY); - const firstAvailableDateInRow = dataUtils.data($row.find(`.${CALENDAR_CELL_CLASS}`).eq(1).get(0), CALENDAR_DATE_VALUE_KEY); - const lastDateInRow = dataUtils.data($row.find(`.${CALENDAR_CELL_CLASS}`).last().get(0), CALENDAR_DATE_VALUE_KEY); - const lastAvailableDateInRow = dataUtils.data($row.find(`.${CALENDAR_CELL_CLASS}`).eq(5).get(0), CALENDAR_DATE_VALUE_KEY); - - $weekNumberCell.trigger('dxclick'); - - const value = this.calendar.option('value'); - - assert.notDeepEqual(value, [firstDateInRow, lastDateInRow], 'disabled dates are not selected as range start/end'); - assert.deepEqual(value, [firstAvailableDateInRow, lastAvailableDateInRow], 'first/last available dates are range start/end'); - }); - - [ - { - selectionMode: 'single', - selectWeekOnClick: true, - expectedCursor: 'auto', - }, - { - selectionMode: 'single', - selectWeekOnClick: false, - expectedCursor: 'auto', - }, - { - selectionMode: 'multiple', - selectWeekOnClick: false, - expectedCursor: 'auto', - }, - { - selectionMode: 'range', - selectWeekOnClick: false, - expectedCursor: 'auto', - }, - { - selectionMode: 'multiple', - selectWeekOnClick: true, - expectedCursor: 'pointer', - }, - { - selectionMode: 'range', - selectWeekOnClick: true, - expectedCursor: 'pointer', - } - ].forEach(({ selectionMode, selectWeekOnClick, expectedCursor }) => { - QUnit.test(`Week number should have "cursor: ${expectedCursor}" style (selectionMode=${selectionMode};selectWeekOnClick=${selectWeekOnClick})`, function(assert) { - this.reinit({ - selectionMode, - selectWeekOnClick, - showWeekNumbers: true, - }); - const cursor = this.$element.find(`.${CALENDAR_WEEK_NUMBER_CELL_CLASS}`).first().css('cursor'); - - assert.strictEqual(cursor, expectedCursor); - }); - }); - }); - }); - - QUnit.module('ViewsCount = 2', { - beforeEach: function() { - this.options = { - focusStateEnabled: true, - value: [new Date('01/15/2023'), new Date('02/05/2023')], - selectionMode: 'range', - viewsCount: 2, - }; - this.reinit(this.options); - this.viewWidth = this.calendar._viewWidth(); - } - }, () => { - QUnit.test('Calendar should not have additional view after runtime multiview disable', function(assert) { - this.calendar.option('viewsCount', 1); - - const additionalView = getAdditionalViewInstance(this.calendar); - - assert.notOk(additionalView); - }); - - QUnit.test('Calendar should have additional view after runtime multiview enable', function(assert) { - this.reinit({ - ...this.options, - viewsCount: 1 - }); - - this.calendar.option('viewsCount', 2); - const additionalView = getAdditionalViewInstance(this.calendar); - - assert.ok(additionalView, undefined); - }); - - QUnit.test('Click on date in additinal view should not trigger views movement', function(assert) { - let $cell = $(getAdditionalViewInstance(this.calendar).$element().find('*[data-value="2023/02/16"]')); - - $cell.trigger('dxclick'); - - $cell = $(getAdditionalViewInstance(this.calendar).$element().find('*[data-value="2023/02/16"]')); - - assert.strictEqual($cell.length, 1); - }); - - QUnit.test('Click on next month date in additinal view should trigger views movement', function(assert) { - let $cell = $(getAdditionalViewInstance(this.calendar).$element().find('*[data-value="2023/03/03"]')); - - $cell.trigger('dxclick'); - - $cell = $(getAdditionalViewInstance(this.calendar).$element().find('*[data-value="2023/02/16"]')); - - assert.strictEqual($cell.length, 0); - }); - - QUnit.test('contouredDate should be moved to additional view after keyboard moving from the last date on main view', function(assert) { - const $cell = $(getCurrentViewInstance(this.calendar).$element().find('*[data-value="2023/01/31"]')); - const keyboard = keyboardMock($(this.calendar._$viewsWrapper)); - - $cell.trigger('dxclick'); - keyboard.press('right'); - - const viewContouredDate = getCurrentViewInstance(this.calendar).option('contouredDate'); - const additionalViewContouredDate = getAdditionalViewInstance(this.calendar).option('contouredDate'); - - assert.strictEqual(viewContouredDate, null); - assert.deepEqual(additionalViewContouredDate, new Date('2023/02/01')); - }); - - [ - { - offset: 2, - button: 'next', - focusedView: 'main' - }, - { - offset: 1, - button: 'next', - focusedView: 'additional' - }, - { - offset: -1, - button: 'previous', - focusedView: 'main' - }, - { - offset: -2, - button: 'previous', - focusedView: 'additional' - }, - ].forEach(({ offset, button, focusedView }) => { - QUnit.test(`Click on ${button} month button should change currentDate on ${offset} months if ${focusedView} view is focused`, function(assert) { - if(focusedView === 'additional') { - const $additionalViewCell = $(getAdditionalViewInstance(this.calendar).$element().find('*[data-value="2023/02/16"]')); - - $additionalViewCell.trigger('dxclick'); - } - - const currentDate = this.calendar.option('currentDate'); - const navigatorButtonClass = button === 'next' ? CALENDAR_NAVIGATOR_NEXT_VIEW_CLASS : CALENDAR_NAVIGATOR_PREVIOUS_VIEW_CLASS; - const $navigatorButton = this.$element.find(`.${navigatorButtonClass}`); - - $navigatorButton.trigger('dxclick'); - - const newCurrentDate = this.calendar.option('currentDate'); - const expectedCurrentDate = new Date(currentDate.setMonth(currentDate.getMonth() + offset)); - - assert.deepEqual(newCurrentDate, expectedCurrentDate); - }); - }); - - - [ - { - currentDate: new Date('04/15/2023'), - offset: 3, - shouldRefresh: true - }, - { - currentDate: new Date('11/15/2022'), - offset: -2, - shouldRefresh: true - }, - { - currentDate: new Date('03/15/2023'), - offset: 2, - shouldRefresh: false - }, - { - currentDate: new Date('12/15/2022'), - offset: -1, - shouldRefresh: false - } - ].forEach(({ currentDate, offset, shouldRefresh }) => { - QUnit.test(`Views should ${shouldRefresh ? '' : 'not'} be refreshed if currentDate change offset is than ${offset} months`, function(assert) { - const spy = sinon.spy(this.calendar, '_refreshViews'); - - this.calendar.option('currentDate', currentDate); - - assert.strictEqual(spy.calledOnce, shouldRefresh); - }); - }); - - [false, true].forEach((rtlEnabled) => { - QUnit.test(`Should double currentDate change on ${rtlEnabled ? 'right' : 'left'} swipe if additionalView is active (rtlEnabled=${rtlEnabled})`, function(assert) { - const calendar = this.calendar; - calendar.option('rtlEnabled', rtlEnabled); - const $cell = $(getAdditionalViewInstance(calendar).$element().find('*[data-value="2023/02/16"]')); - - $cell.trigger('dxclick'); - const currentDate = calendar.option('currentDate'); - const pointer = pointerMock(this.$element).start(); - - pointer.swipeStart().swipeEnd(0.5 * rtlEnabled ? -1 : 1); - - const expectedCurrentDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - 2, currentDate.getDate()); - const newCurrentDate = calendar.option('currentDate'); - - assert.deepEqual(newCurrentDate, expectedCurrentDate); - }); - }); - }); -}); - - -QUnit.module('ZoomLevel option', { - beforeEach: function() { - fx.off = true; - this.$element = $('
').appendTo('#qunit-fixture'); - }, - afterEach: function() { - this.$element.remove(); - fx.off = false; - } -}, () => { - QUnit.test('\'zoomLevel\' should have correct value on init if \'maxZoomLevel\' is specified', function(assert) { - const calendar = this.$element.dxCalendar({ - maxZoomLevel: 'year', - zoomLevel: 'month' - }).dxCalendar('instance'); - - assert.equal(calendar.option('zoomLevel'), calendar.option('maxZoomLevel'), '\'zoomLevel\' is corrected'); - }); - - QUnit.test('view should not be changed down if specified maxZoomLevel is reached', function(assert) { - const calendar = this.$element.dxCalendar({ - maxZoomLevel: 'year', - zoomLevel: 'decade' - }).dxCalendar('instance'); - - $(this.$element.find(`.${CALENDAR_CELL_CLASS}`).eq(5)).trigger('dxclick'); - assert.equal(calendar.option('zoomLevel'), 'year', '\'zoomLevel\' changed'); - - $(this.$element.find(`.${CALENDAR_CELL_CLASS}`).eq(5)).trigger('dxclick'); - assert.equal(calendar.option('zoomLevel'), 'year', '\'zoomLevel\' did not change'); - }); - - QUnit.test('\'zoomLevel\' should be aligned after \'maxZoomLevel\' option change if out of bounds', function(assert) { - const calendar = this.$element.dxCalendar({ - maxZoomLevel: 'month', - value: new Date(2015, 2, 15) - }).dxCalendar('instance'); - - $.each(['month', 'year', 'decade', 'century'], (_, type) => { - calendar.option('maxZoomLevel', type); - - assert.equal(calendar.option('zoomLevel'), type, 'calendar \'zoomLevel\' is correct'); - }); - }); - - QUnit.test('\'zoomLevel\' option should not be changed after \'maxZoomLevel\' option change', function(assert) { - const calendar = this.$element.dxCalendar({ - maxZoomLevel: 'century', - value: new Date(2015, 2, 15) - }).dxCalendar('instance'); - - $.each(['month', 'year', 'decade', 'century'], (_, type) => { - calendar.option('maxZoomLevel', type); - - assert.equal(calendar.option('zoomLevel'), 'century', 'calendar \'zoomLevel\' is correct'); - }); - }); - - QUnit.test('calendar should get correct value after click on cell of specified maxZoomLevel', function(assert) { - const calendar = this.$element.dxCalendar({ - maxZoomLevel: 'year', - value: new Date(2015, 2, 15) - }).dxCalendar('instance'); - - $(this.$element.find(`.${CALENDAR_CELL_CLASS}`).eq(5)).trigger('dxclick'); - assert.deepEqual(calendar.option('value'), new Date(2015, 5, 1), '\'zoomLevel\' changed'); - - calendar.option('maxZoomLevel', 'decade'); - $(this.$element.find(`.${CALENDAR_CELL_CLASS}`).eq(5)).trigger('dxclick'); - assert.deepEqual(calendar.option('value'), new Date(2014, 0, 1), '\'zoomLevel\' changed'); - - calendar.option('maxZoomLevel', 'century'); - $(this.$element.find(`.${CALENDAR_CELL_CLASS}`).eq(5)).trigger('dxclick'); - assert.deepEqual(calendar.option('value'), new Date(2040, 0, 1), '\'zoomLevel\' changed'); - }); - - QUnit.test('do not go up if minZoomLevel is reached', function(assert) { - const $element = this.$element; - const instance = $element.dxCalendar().dxCalendar('instance'); - - $.each(['month', 'year', 'decade'], (_, type) => { - instance.option({ - minZoomLevel: type, - zoomLevel: type - }); - - $(`.${CALENDAR_CAPTION_BUTTON_CLASS}`).trigger('dxclick'); - assert.equal(instance.option('zoomLevel'), type, 'zoom level did not change'); - }); - }); - - QUnit.test('\'zoomLevel\' should be aligned after \'minZoomLevel\' option change if out of bounds', function(assert) { - const $element = this.$element; - const instance = $element.dxCalendar({ - minZoomLevel: 'century', - zoomLevel: 'century' - }).dxCalendar('instance'); - - $.each(['decade', 'year', 'month'], (_, type) => { - instance.option('minZoomLevel', type); - assert.equal(instance.option('zoomLevel'), type, 'zoom level is changed correctly'); - }); - }); - - QUnit.test('cancel change zoomLevel if there is only one cell on new view', function(assert) { - const calendar = this.$element.dxCalendar({ - maxZoomLevel: 'month', - min: new Date(2015, 3, 5), - max: new Date(2015, 3, 25), - value: new Date(2015, 2, 15) - }).dxCalendar('instance'); - - const $captionButton = this.$element.find(`.${CALENDAR_CAPTION_BUTTON_CLASS}`); - - $($captionButton).trigger('dxclick'); - assert.equal(calendar.option('zoomLevel'), 'month', 'view is not changed (month)'); - - calendar.option('zoomLevel', 'year'); - calendar.option('max', new Date(2015, 6, 25)); - $($captionButton).trigger('dxclick'); - assert.equal(calendar.option('zoomLevel'), 'year', 'view is not changed (year)'); - - calendar.option('zoomLevel', 'decade'); - calendar.option('max', new Date(2017, 6, 25)); - $($captionButton).trigger('dxclick'); - assert.equal(calendar.option('zoomLevel'), 'decade', 'view is not changed (decade)'); - }); - - QUnit.test('change ZoomLevel after click on view cell', function(assert) { - const $element = this.$element; - const calendar = $element.dxCalendar({ - zoomLevel: 'century', - value: new Date(2015, 2, 15) - }).dxCalendar('instance'); - - $.each(['century', 'decade'], (_, type) => { - calendar.option('zoomLevel', type); - - $($element.find(`.${CALENDAR_CELL_CLASS}`).not(`.${CALENDAR_OTHER_VIEW_CLASS}`).eq(3)).trigger('dxclick'); - assert.notStrictEqual(calendar.option('zoomLevel'), type, 'zoomLevel option view is changed'); - }); - }); - - QUnit.test('change ZoomLevel after pressing enter key on view cell', function(assert) { - const $element = this.$element; - const calendar = $element.dxCalendar({ - zoomLevel: 'century', - value: new Date(2015, 2, 15), - focusStateEnabled: true - }).dxCalendar('instance'); - - $.each(['century', 'decade'], (_, type) => { - calendar.option('zoomLevel', type); - calendar.focus(); - triggerKeydown($(calendar._$viewsWrapper), ENTER_KEY_CODE); - assert.notStrictEqual(calendar.option('zoomLevel'), type, 'zoomLevel option view is changed'); - }); - }); - - QUnit.test('change ZoomLevel after click on other view cell', function(assert) { - const $element = this.$element; - const calendar = $element.dxCalendar({ - zoomLevel: 'century', - value: new Date(2015, 2, 15) - }).dxCalendar('instance'); - - $.each(['century', 'decade'], (_, type) => { - calendar.option('zoomLevel', type); - - $($element.find(`.${CALENDAR_OTHER_VIEW_CLASS}`).first()).trigger('dxclick'); - assert.notStrictEqual(calendar.option('zoomLevel'), type, 'zoomLevel option view is changed'); - }); - }); - - QUnit.test('Current view should be set correctly, after click on other view cells', function(assert) { - - const $element = this.$element; - const calendar = $element.dxCalendar({ - value: new Date(2015, 1, 1), - zoomLevel: 'decade' - }).dxCalendar('instance'); - - const spy = sinon.spy(calendar, '_navigate'); - - try { - fx.off = false; - this.clock = sinon.useFakeTimers(); - $($element.find(`.${CALENDAR_CELL_CLASS}`).first()).trigger('dxclick'); - - this.clock.tick(1000); - - const navigatorCaptionText = $element.find(`.${CALENDAR_CAPTION_BUTTON_CLASS}`).text(); - const dataCell = $element.find(`.${CALENDAR_CELL_CLASS}`).first().data('value'); - - assert.equal(navigatorCaptionText, '2009', 'navigator caption text is correct'); - assert.equal(dataCell, '2009/01/01', 'cell data is correct'); - assert.ok(!spy.called, '_navigate should not be called'); - assert.equal(calendar.option('zoomLevel'), 'year'); - } finally { - fx.off = true; - this.clock.restore(); - } - }); - - QUnit.test('Month names should be shown in \'abbreviated\' format when ZoomLevel is Year', function(assert) { - const getMonthNamesStub = sinon.stub(dateLocalization, 'getMonthNames'); - - getMonthNamesStub.returns(['leden', 'únor', 'březen', 'duben', 'květen', 'červen', 'červenec', 'srpen', 'září', 'říjen', 'listopad', 'prosinec']); - getMonthNamesStub.withArgs('abbreviated').returns(['led', 'úno', 'bře', 'dub', 'kvě', 'čvn', 'čvc', 'srp', 'zář', 'říj', 'lis', 'pro']); - - const calendar = this.$element.dxCalendar({ - zoomLevel: 'year', - value: new Date(2017, 10, 20) - }).dxCalendar('instance'); - - const $cells = $(getCurrentViewInstance(calendar).$element().find('.dx-calendar-cell')); - - assert.equal($cells.eq(5).text().trim(), 'čvn'); - assert.equal($cells.eq(6).text().trim(), 'čvc'); - - getMonthNamesStub.restore(); - }); -}); - - -QUnit.module('Min & Max options', { - beforeEach: function() { - fx.off = true; - - this.value = new Date(2010, 10, 10); - this.minDate = new Date(2010, 9, 10); - this.maxDate = new Date(2010, 11, 10); - - this.$element = $('
').appendTo('#qunit-fixture'); - this.calendar = this.$element.dxCalendar({ - min: this.minDate, - value: this.value, - max: this.maxDate, - focusStateEnabled: true - }).dxCalendar('instance'); - - this.clock = sinon.useFakeTimers(); - - this.reinit = (options) => { - this.$element.remove(); - this.$element = $('
').appendTo('#qunit-fixture'); - this.calendar = this.$element.dxCalendar(options).dxCalendar('instance'); - }; - }, - afterEach: function() { - this.$element.remove(); - this.clock.restore(); - fx.off = false; - } -}, () => { - QUnit.test('calendar should not throw error if max date is null', function(assert) { - assert.expect(0); - - new Calendar('
', { value: new Date(2013, 9, 15), firstDayOfWeek: 1, max: null }); - }); - - QUnit.test('calendar must pass min and max to the created views', function(assert) { - assert.deepEqual(getCurrentViewInstance(this.calendar).option('min'), this.minDate); - assert.deepEqual(getCurrentViewInstance(this.calendar).option('max'), this.maxDate); - }); - - QUnit.test('calendar should not allow to navigate to a date earlier than min and later than max via keyboard events', function(assert) { - const isAnimationOff = fx.off; - const animate = fx.animate; - - try { - let animateCount = 0; - - fx.off = false; - - fx.animate = (...args) => { - animateCount++; - return animate.apply(fx, args); - }; - - const minimumCurrentDate = new Date(this.value.getFullYear(), this.value.getMonth() - 1, this.value.getDate()); - const currentDate = new Date(this.value.getFullYear(), this.value.getMonth(), this.value.getDate()); - const maximumCurrentDate = new Date(this.value.getFullYear(), this.value.getMonth() + 1, this.value.getDate()); - - const calendar = this.calendar; - const $viewsWrapper = $(calendar._$viewsWrapper); - - calendar.focus(); - - triggerKeydown($viewsWrapper, PAGE_UP_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(this.calendar.option('currentDate'), minimumCurrentDate); - assert.equal(animateCount, 1, 'view is changed with animation after the \'page up\' key press the first time'); - - triggerKeydown($viewsWrapper, PAGE_UP_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(this.calendar.option('currentDate'), minimumCurrentDate); - assert.equal(animateCount, 1, 'view is not changed after the \'page up\' key press the second time'); - - triggerKeydown($viewsWrapper, PAGE_DOWN_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(this.calendar.option('currentDate'), currentDate); - - triggerKeydown($viewsWrapper, PAGE_DOWN_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(this.calendar.option('currentDate'), maximumCurrentDate); - assert.equal(animateCount, 3, 'view is changed with animation after the \'page down\' key press the first time'); - - triggerKeydown($viewsWrapper, PAGE_DOWN_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(this.calendar.option('currentDate'), maximumCurrentDate); - assert.equal(animateCount, 3, 'view is not changed after the \'page down\' key press the second time'); - } finally { - fx.off = isAnimationOff; - fx.animate = animate; - } - }); - - QUnit.test('calendar should set currentDate to min when setting to an earlier date; and to max when setting to a later date', function(assert) { - const calendar = this.calendar; - const min = calendar.option('min'); - const max = calendar.option('max'); - const earlyDate = new Date(this.minDate.getFullYear(), this.minDate.getMonth() - 1, 1); - const lateDate = new Date(this.maxDate.getFullYear(), this.maxDate.getMonth() + 1, 1); - - calendar.option('currentDate', earlyDate); - assert.deepEqual(calendar.option('currentDate'), new Date(this.minDate.getFullYear(), this.minDate.getMonth(), min.getDate())); - calendar.option('currentDate', lateDate); - assert.deepEqual(calendar.option('currentDate'), new Date(this.maxDate.getFullYear(), this.maxDate.getMonth(), max.getDate())); - }); - - QUnit.test('calendar should properly initialize currentDate with respect to min and max', function(assert) { - this.reinit({ - min: this.minDate, - max: this.maxDate - }); - - const calendar = this.calendar; - assert.ok(dateUtils.sameView(calendar.option('zoomLevel'), calendar.option('currentDate'), this.minDate)); - }); - - QUnit.test('value should not be changed when min and max options are set', function(assert) { - const calendar = this.calendar; - const outOfRangeDate = new Date(2010, 12, 10); - - calendar.option('value', outOfRangeDate); - assert.equal(calendar.option('value'), outOfRangeDate, 'value is not changed'); - }); - - QUnit.test('current date is max month if value is null and range is earlier than today', function(assert) { - this.reinit({ - min: this.minDate, - max: this.maxDate, - currentDate: new Date(2015, 10, 13), - value: null - }); - - const calendar = this.calendar; - - assert.strictEqual(calendar.option('value'), null, 'value is null'); - assert.deepEqual(calendar.option('currentDate'), new Date(this.maxDate), 'current date is max'); - }); - - QUnit.test('change currentDate without navigation if became out of range after max is set', function(assert) { - this.reinit({ - value: new Date(2015, 5, 16) - }); - - const spy = sinon.spy(this.calendar, '_navigate'); - const max = new Date(2015, 4, 7); - - this.calendar.option('max', max); - assert.deepEqual(this.calendar.option('currentDate'), max, 'currentDate and max are equal'); - assert.equal(spy.callCount, 0, 'there was no navigation'); - assert.equal(this.$element.find(`.${CALENDAR_CAPTION_BUTTON_CLASS}`).text(), 'May 2015', 'navigator caption is changed'); - }); - - QUnit.test('change currentDate without navigation if became out of range after min is set', function(assert) { - this.reinit({ - value: new Date(2015, 5, 16) - }); - - const spy = sinon.spy(this.calendar, '_navigate'); - const min = new Date(2015, 6, 12); - - this.calendar.option('min', min); - assert.deepEqual(this.calendar.option('currentDate'), min, 'currentDate and min are equal'); - assert.equal(spy.callCount, 0, 'there was no navigation'); - assert.equal(this.$element.find(`.${CALENDAR_CAPTION_BUTTON_CLASS}`).text(), 'July 2015', 'navigator caption is changed'); - }); - - QUnit.test('current date is not changed when min or max option is changed and current value is in range', function(assert) { - const value = new Date(2015, 0, 27); - - this.reinit({ - min: null, - max: null, - value: value - }); - - const calendar = this.calendar; - const minDate = new Date(value); - const maxDate = new Date(value); - - minDate.setYear(2014); - maxDate.setYear(2015); - - assert.deepEqual(calendar.option('currentDate'), value, 'current date and value are the same'); - - calendar.option('min', minDate); - assert.deepEqual(calendar.option('currentDate'), value, 'current date and min are the same after min option is set'); - assert.deepEqual(calendar.option('value'), value, 'value is not changed'); - - calendar.option('min', null); - assert.deepEqual(calendar.option('currentDate'), value, 'current date and value are the same'); - assert.deepEqual(calendar.option('value'), value, 'value is not changed'); - - calendar.option('max', maxDate); - assert.deepEqual(calendar.option('currentDate'), value, 'current date and max are the same after max option is set'); - assert.deepEqual(calendar.option('value'), value, 'value is not changed'); - }); - - QUnit.test('T278441 - min date should be 1/1/1000 if the \'min\' option is null', function(assert) { - const value = new Date(988, 7, 17); - - this.reinit({ - value: value, - min: null - }); - - assert.deepEqual(this.calendar.option('currentDate'), new Date(1000, 0), 'current date is correct'); - }); - - QUnit.test('T278441 - max date should be 31/12/2999 if the \'max\' option is null', function(assert) { - const value = new Date(3015, 7, 17); - - this.reinit({ - value: value, - max: null - }); - - assert.deepEqual(this.calendar.option('currentDate'), new Date(3000, 0), 'current date is correct'); - }); - - QUnit.test('T266658 - widget should have no views that are out of range', function(assert) { - this.reinit({ - value: new Date(2015, 8, 8), - min: new Date(2015, 8, 2), - max: new Date(2015, 9, 20) - }); - - const calendar = this.calendar; - const $viewsWrapper = $(calendar.$element().find(`.${CALENDAR_VIEWS_WRAPPER_CLASS}`)); - - assert.equal($viewsWrapper.children().length, 2, 'the number of views is correct when current view contain min date'); - assert.ok(!getBeforeViewInstance(calendar), 'there is no after view'); - - calendar.option('value', new Date(2015, 9, 15)); - - assert.equal($viewsWrapper.children().length, 2, 'the number of views is correct when current view contain max date'); - assert.ok(!getAfterViewInstance(calendar), 'there is no after view'); - }); - - QUnit.test('T266658 - widget should have no views that are out of range after navigation', function(assert) { - this.reinit({ - value: new Date(2015, 9, 8), - min: new Date(2015, 8, 2), - max: new Date(2015, 9, 20) - }); - - const calendar = this.calendar; - const $views = $(calendar.$element().find(`.${CALENDAR_VIEWS_WRAPPER_CLASS}`).children()); - - assert.equal($views.length, 2, 'the number of views is correct when current view contain min date'); - }); - - QUnit.test('correct views rendering with min option', function(assert) { - const params = { - 'year': { value: new Date(2015, 0, 8), min: new Date(2014, 11, 16) }, - 'decade': { value: new Date(2010, 0, 8), min: new Date(2009, 11, 16) }, - 'century': { value: new Date(2000, 0, 8), min: new Date(1999, 11, 16) } - }; - - $.each(['year', 'decade', 'century'], $.proxy((_, type) => { - this.reinit($.extend({}, params[type], { zoomLevel: type })); - - const $views = this.$element.find(`.${CALENDAR_VIEWS_WRAPPER_CLASS}`).children(); - assert.equal($views.length, 3, 'all three views are rendered'); - }, this)); - }); - - QUnit.test('correct views rendering with max option', function(assert) { - const params = { - 'year': { value: new Date(2015, 11, 8), max: new Date(2016, 0, 16) }, - 'decade': { value: new Date(2019, 11, 8), max: new Date(2020, 0, 16) }, - 'century': { value: new Date(2099, 11, 8), max: new Date(2100, 0, 16) } - }; - - $.each(['year', 'decade', 'century'], $.proxy((_, type) => { - this.reinit($.extend({}, params[type], { zoomLevel: type })); - - const $views = this.$element.find(`.${CALENDAR_VIEWS_WRAPPER_CLASS}`).children(); - assert.equal($views.length, 3, 'all three views are rendered'); - }, this)); - }); -}); - - -QUnit.module('disabledDates option', { - beforeEach: function() { - fx.off = true; - - this.value = new Date(2010, 10, 10); - this.disabledDates = (args) => { - const month = args.date.getMonth(); - - if(month === 9 || month === 11) { - return true; - } - }; - - this.$element = $('
').appendTo('#qunit-fixture'); - this.calendar = this.$element.dxCalendar({ - disabledDates: this.disabledDates, - value: this.value, - focusStateEnabled: true - }).dxCalendar('instance'); - - this.clock = sinon.useFakeTimers(); - - this.reinit = (options) => { - this.$element.remove(); - this.$element = $('
').appendTo('#qunit-fixture'); - this.calendar = this.$element.dxCalendar(options).dxCalendar('instance'); - }; - }, - afterEach: function() { - this.$element.remove(); - this.clock.restore(); - fx.off = false; - } -}, () => { - QUnit.test('navigating to the disabled month should not skip the month and should focus the current date', function(assert) { - const isAnimationOff = fx.off; - const animationSpy = sinon.spy(fx, 'animate'); - - try { - fx.off = false; - - const calendar = this.calendar; - const $viewsWrapper = $(calendar._$viewsWrapper); - - calendar.focus(); - - triggerKeydown($viewsWrapper, PAGE_UP_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), new Date(2010, 9, 10), 'same date has been focused'); - assert.equal(animationSpy.callCount, 1, 'view is changed with animation after the \'page up\' key press the first time'); - - triggerKeydown($viewsWrapper, PAGE_UP_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), new Date(2010, 8, 10), 'same date has been focused'); - assert.equal(animationSpy.callCount, 2, 'view is changed after the \'page up\' key press the second time'); - - triggerKeydown($viewsWrapper, PAGE_DOWN_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), new Date(2010, 9, 10), 'same date has been focused'); - assert.equal(animationSpy.callCount, 3, 'view is changed with animation after the \'page down\' key press the first time'); - - triggerKeydown($viewsWrapper, PAGE_DOWN_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), new Date(2010, 10, 10), 'same date has been focused'); - assert.equal(animationSpy.callCount, 4, 'view is changed with animation after the \'page down\' key press the first time'); - - triggerKeydown($viewsWrapper, PAGE_DOWN_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), new Date(2010, 11, 10), 'same date has been focused'); - assert.equal(animationSpy.callCount, 5, 'view is changed after the \'page down\' key press the third time'); - - $(this.$element.find(`.${CALENDAR_NAVIGATOR_PREVIOUS_VIEW_CLASS}`)).trigger('dxclick'); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(this.calendar.option('currentDate'), new Date(2010, 10, 10), 'same date has been focused'); - assert.equal(animationSpy.callCount, 6, 'view is changed after the click on previous arrow on UI'); - - $(this.$element.find(`.${CALENDAR_NAVIGATOR_NEXT_VIEW_CLASS}`)).trigger('dxclick'); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), new Date(2010, 11, 10), 'same date has been focused'); - assert.equal(animationSpy.callCount, 7, 'view is changed after the click on next arrow on UI'); - } finally { - fx.off = isAnimationOff; - animationSpy.restore(); - } - }); - - QUnit.test('navigating to next/previous month should focus the closest available date and change the view', function(assert) { - const isAnimationOff = fx.off; - const animationSpy = sinon.spy(fx, 'animate'); - - try { - const calendar = this.calendar; - calendar.option({ - value: new Date(2020, 0, 15), - disabledDates: (args) => { - const date = args.date.getDate(); - const month = args.date.getMonth(); - return month === 0 && date >= 20 || month === 1 && date < 20; - } - }); - const $viewsWrapper = $(calendar._$viewsWrapper); - - fx.off = false; - animationSpy.resetHistory(); - - const lastAvailableDateOnJanuary = new Date(2020, 0, 19); - const firstAvailableDateOnFebruary = new Date(2020, 1, 20); - calendar.focus(); - - triggerKeydown($viewsWrapper, PAGE_DOWN_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), firstAvailableDateOnFebruary, 'closest available date has been focused'); - assert.equal(animationSpy.callCount, 1, 'view has been changed'); - - triggerKeydown($viewsWrapper, PAGE_UP_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), lastAvailableDateOnJanuary, 'closest available date has been focused'); - assert.equal(animationSpy.callCount, 2, 'view has been changed'); - - triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE, { ctrlKey: true }); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), firstAvailableDateOnFebruary, 'closest available date has been focused'); - assert.equal(animationSpy.callCount, 3, 'view has been changed'); - - triggerKeydown($viewsWrapper, LEFT_ARROW_KEY_CODE, { ctrlKey: true }); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), lastAvailableDateOnJanuary, 'closest available date has been focused'); - assert.equal(animationSpy.callCount, 4, 'view has been changed'); - - $(this.$element.find(`.${CALENDAR_NAVIGATOR_NEXT_VIEW_CLASS}`)).trigger('dxclick'); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), firstAvailableDateOnFebruary, 'closest available date has been focused'); - assert.equal(animationSpy.callCount, 5, 'view has been changed'); - - $(this.$element.find(`.${CALENDAR_NAVIGATOR_PREVIOUS_VIEW_CLASS}`)).trigger('dxclick'); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), lastAvailableDateOnJanuary, 'closest available date has been focused'); - assert.equal(animationSpy.callCount, 6, 'view has been changed'); - } finally { - fx.off = isAnimationOff; - animationSpy.restore(); - } - }); - - QUnit.test('left/right/up/downArrow should focus the closest date on the previous/next month when forced to change the month', function(assert) { - const isAnimationOff = fx.off; - const animationSpy = sinon.spy(fx, 'animate'); - - try { - const calendar = this.calendar; - - calendar.option({ - value: new Date(2020, 0, 14), - disabledDates: (args) => { - return args.date.getDate() >= 15 || args.date.getDate() <= 4; - } - }); - const $viewsWrapper = $(calendar._$viewsWrapper); - - fx.off = false; - animationSpy.resetHistory(); - - calendar.focus(); - - triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), new Date(2020, 1, 5), 'closest available date has been focused'); - assert.equal(animationSpy.callCount, 1, 'view has been changed'); - - triggerKeydown($viewsWrapper, LEFT_ARROW_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 14), 'closest available date has been focused'); - assert.equal(animationSpy.callCount, 2, 'view has been changed'); - - triggerKeydown($viewsWrapper, DOWN_ARROW_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), new Date(2020, 1, 11), 'closest available date has been focused'); - assert.equal(animationSpy.callCount, 3, 'view has been changed'); - - triggerKeydown($viewsWrapper, UP_ARROW_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 14), 'closest available date has been focused'); - assert.equal(animationSpy.callCount, 4, 'view has been changed'); - } finally { - fx.off = isAnimationOff; - animationSpy.restore(); - } - }); - - QUnit.test('left/right/up/downArrow should try focus the date moved by offset in a month', function(assert) { - const calendar = this.calendar; - - calendar.option({ - value: new Date(2020, 0, 6), - disabledDates: (args) => { - const date = args.date.getDate(); - return date > 10 && date < 16 || date === 7 || date === 21; - } - }); - const $viewsWrapper = $(calendar._$viewsWrapper); - - calendar.focus(); - - triggerKeydown($viewsWrapper, DOWN_ARROW_KEY_CODE); - assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 20), 'closest date by offset has been focused'); - - triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE); - assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 22), 'closest date by offset has been focused'); - - triggerKeydown($viewsWrapper, UP_ARROW_KEY_CODE); - assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 8), 'closest date by offset has been focused'); - - triggerKeydown($viewsWrapper, LEFT_ARROW_KEY_CODE); - assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 6), 'closest date by offset has been focused'); - }); - - QUnit.test('left/right arrows should try focus the month moved by offset in a year view', function(assert) { - const calendar = this.calendar; - - calendar.option({ - value: new Date(2020, 0, 6), - zoomLevel: 'year', - disabledDates: (args) => { - const month = args.date.getMonth(); - return month % 2; - } - }); - const $viewsWrapper = $(calendar._$viewsWrapper); - - calendar.focus(); - - triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE); - assert.deepEqual(calendar.option('currentDate'), new Date(2020, 2, 6), 'closest month by offset has been focused'); - - triggerKeydown($viewsWrapper, LEFT_ARROW_KEY_CODE); - assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 6), 'closest month by offset has been focused'); - }); - - QUnit.test('left/right/up/down arrows should try focus the year moved by offset in a decade view', function(assert) { - const calendar = this.calendar; - - calendar.option({ - value: new Date(2020, 0, 6), - zoomLevel: 'decade', - disabledDates: (args) => { - const year = args.date.getYear(); - return year === 121 || year === 124; - } - }); - const $viewsWrapper = $(calendar._$viewsWrapper); - - calendar.focus(); - - triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE); - assert.deepEqual(calendar.option('currentDate'), new Date(2022, 0, 6), 'closest year by offset has been focused'); - - triggerKeydown($viewsWrapper, LEFT_ARROW_KEY_CODE); - assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 6), 'closest year by offset has been focused'); - - triggerKeydown($viewsWrapper, DOWN_ARROW_KEY_CODE); - assert.deepEqual(calendar.option('currentDate'), new Date(2028, 0, 6), 'closest year by offset has been focused'); - - triggerKeydown($viewsWrapper, UP_ARROW_KEY_CODE); - assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 6), 'closest year by offset has been focused'); - }); - - QUnit.test('left/right/up/down arrows should try focus the decade moved by offset in a century view', function(assert) { - const calendar = this.calendar; - - calendar.option({ - value: new Date(2000, 0, 6), - zoomLevel: 'century', - disabledDates: (args) => { - const year = args.date.getYear(); - return year >= 110 && year < 120 || year >= 140 && year < 150; - } - }); - const $viewsWrapper = $(calendar._$viewsWrapper); - - calendar.focus(); - - triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE); - assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 6), 'closest decade by offset has been focused'); - - triggerKeydown($viewsWrapper, LEFT_ARROW_KEY_CODE); - assert.deepEqual(calendar.option('currentDate'), new Date(2000, 0, 6), 'closest decade by offset has been focused'); - - triggerKeydown($viewsWrapper, DOWN_ARROW_KEY_CODE); - assert.deepEqual(calendar.option('currentDate'), new Date(2080, 0, 6), 'closest decade by offset has been focused'); - - triggerKeydown($viewsWrapper, UP_ARROW_KEY_CODE); - assert.deepEqual(calendar.option('currentDate'), new Date(2000, 0, 6), 'closest decade by offset has been focused'); - }); - - QUnit.test('disabled decade/century should not be skipped during navigation', function(assert) { - const calendar = this.calendar; - - calendar.option({ - value: new Date(2020, 0, 6), - zoomLevel: 'decade', - disabledDates: (args) => { - const view = args.view; - const year = args.date.getYear(); - if(view === 'decade') { - return year >= 130 && year < 140; - } else if(view === 'century') { - return year >= 100 && year < 200; - } - } - }); - const $viewsWrapper = $(calendar._$viewsWrapper); - - calendar.focus(); - - triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE, { ctrlKey: true }); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), new Date(2030, 0, 6), 'current date is correct'); - - triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), new Date(2040, 0, 6), 'current date is correct'); - - triggerKeydown($viewsWrapper, UP_ARROW_KEY_CODE, { ctrlKey: true }); - this.clock.tick(VIEW_ANIMATION_DURATION); - - triggerKeydown($viewsWrapper, LEFT_ARROW_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), new Date(1940, 0, 6), 'current date is correct'); - }); - - QUnit.test('up/down arrows should try focus the month moved by offset in a year view', function(assert) { - const calendar = this.calendar; - - calendar.option({ - value: new Date(2020, 4, 6), - zoomLevel: 'year', - disabledDates: (args) => { - const month = args.date.getMonth(); - return month < 4 || month > 7; - } - }); - const $viewsWrapper = $(calendar._$viewsWrapper); - - calendar.focus(); - - triggerKeydown($viewsWrapper, UP_ARROW_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), new Date(2019, 4, 6), 'closest month by offset has been focused'); - - triggerKeydown($viewsWrapper, DOWN_ARROW_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), new Date(2020, 4, 6), 'closest month by offset has been focused'); - }); - - QUnit.test('zoomLevel option change should focus the closest available date', function(assert) { - const calendar = this.calendar; - - calendar.option({ - value: new Date(2020, 0, 6), - zoomLevel: 'year', - disabledDates: (args) => { - const year = args.date.getYear(); - const date = args.date.getDate(); - - if(args.view === 'decade') { - return year === 120; - } - - return date === 6; - } - }); - const $viewsWrapper = $(calendar._$viewsWrapper); - - calendar.focus(); - - triggerKeydown($viewsWrapper, DOWN_ARROW_KEY_CODE, { ctrlKey: true }); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 7), 'closest date has been focused'); - - triggerKeydown($viewsWrapper, UP_ARROW_KEY_CODE, { ctrlKey: true }); - this.clock.tick(VIEW_ANIMATION_DURATION); - - triggerKeydown($viewsWrapper, UP_ARROW_KEY_CODE, { ctrlKey: true }); - this.clock.tick(VIEW_ANIMATION_DURATION); - - assert.deepEqual(calendar.option('currentDate'), new Date(2021, 0, 7), 'closest date has been focused'); - }); - - QUnit.test('zoomLevel option change should contour the current view even if current date has not been changed', function(assert) { - const currentDate = new Date(2020, 0, 6); - const calendar = this.calendar; - const $viewsWrapper = $(calendar._$viewsWrapper); - - calendar.option({ - value: currentDate, - }); - - calendar.focus(); - - triggerKeydown($viewsWrapper, UP_ARROW_KEY_CODE, { ctrlKey: true }); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), currentDate, 'currentDate has not been changed'); - assert.deepEqual(calendar._view.option('contouredDate'), currentDate, 'contoured date is correct'); - }); - - QUnit.test('left/right/up/downArrow should work like pageUp/Down when navigating to the disabled month', function(assert) { - const isAnimationOff = fx.off; - const animationSpy = sinon.spy(fx, 'animate'); - - try { - const currentDateOnJanuary = new Date(2020, 0, 15); - const currentDateOnFebruary = new Date(2020, 1, 15); - const currentDateOnMarch = new Date(2020, 2, 15); - const currentDateOnApril = new Date(2020, 3, 15); - const calendar = this.calendar; - - calendar.option({ - value: currentDateOnJanuary, - disabledDates: (args) => { - const month = args.date.getMonth(); - return month === 1 || month === 2; - } - }); - const $viewsWrapper = $(calendar._$viewsWrapper); - - fx.off = false; - animationSpy.resetHistory(); - - calendar.focus(); - - triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE, { ctrlKey: true }); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), currentDateOnFebruary, 'the same date has been focused'); - assert.equal(animationSpy.callCount, 1, 'view has been changed'); - - triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), currentDateOnMarch, 'the same date has been focused'); - assert.equal(animationSpy.callCount, 2, 'view has been changed'); - - triggerKeydown($viewsWrapper, DOWN_ARROW_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), currentDateOnApril, 'the same date has been focused'); - assert.equal(animationSpy.callCount, 3, 'view has been changed'); - - triggerKeydown($viewsWrapper, LEFT_ARROW_KEY_CODE, { ctrlKey: true }); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), currentDateOnMarch, 'the same date has been focused'); - assert.equal(animationSpy.callCount, 4, 'view has been changed'); - - triggerKeydown($viewsWrapper, LEFT_ARROW_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), currentDateOnFebruary, 'the same date has been focused'); - assert.equal(animationSpy.callCount, 5, 'view has been changed'); - - triggerKeydown($viewsWrapper, UP_ARROW_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), currentDateOnJanuary, 'the same date has been focused'); - assert.equal(animationSpy.callCount, 6, 'view has been changed'); - - } finally { - fx.off = isAnimationOff; - animationSpy.restore(); - } - }); - - QUnit.test('calendar should properly set the first and the last available cells', function(assert) { - this.reinit({ - disabledDates: (args) => { - const disabledDays = [1, 2, 28, 30]; - if(disabledDays.indexOf(args.date.getDate()) > -1) { - return true; - } - }, - value: this.value, - focusStateEnabled: true - }); - const calendar = this.calendar; - const $viewsWrapper = $(calendar._$viewsWrapper); - - calendar.focus(); - - triggerKeydown($viewsWrapper, HOME_KEY_CODE); - assert.deepEqual(calendar.option('currentDate'), new Date(2010, 10, 3)); - - triggerKeydown($viewsWrapper, END_KEY_CODE); - assert.deepEqual(calendar.option('currentDate'), new Date(2010, 10, 29)); - }); - - QUnit.test('home/end keys should not do anything if all dates in the current month are disabled', function(assert) { - const calendar = this.calendar; - const $viewsWrapper = $(calendar._$viewsWrapper); - - calendar.option('value', new Date(2010, 11, 10)); - - calendar.focus(); - - triggerKeydown($viewsWrapper, HOME_KEY_CODE); - assert.deepEqual(calendar.option('currentDate'), this.calendar.option('value')); - - triggerKeydown($viewsWrapper, END_KEY_CODE); - assert.deepEqual(calendar.option('currentDate'), this.calendar.option('value')); - }); - - QUnit.test('enter key should not change selected value if focused date is disabled', function(assert) { - const startDate = new Date(2020, 0, 6); - const calendar = this.calendar; - - calendar.option({ - value: startDate, - disabledDates: (args) => { - return args.date.getMonth() === 1; - } - }); - const $viewsWrapper = $(calendar._$viewsWrapper); - - calendar.focus(); - - triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE, { ctrlKey: true }); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), new Date(2020, 1, 6), 'current date is correct'); - - triggerKeydown($viewsWrapper, ENTER_KEY_CODE); - assert.deepEqual(calendar.option('value'), startDate, 'selected value has not been changed'); - }); - - QUnit.test('enter key should change selected value if focused date is not disabled', function(assert) { - const startDate = new Date(2020, 0, 6); - const newDate = new Date(2020, 1, 6); - const calendar = this.calendar; - const $viewsWrapper = $(calendar._$viewsWrapper); - - calendar.option({ - value: startDate, - }); - - calendar.focus(); - - triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE, { ctrlKey: true }); - - assert.deepEqual(calendar.option('currentDate'), newDate, 'current date has been changed'); - assert.deepEqual(calendar.option('value'), startDate, 'selected value is correct'); - - triggerKeydown($viewsWrapper, ENTER_KEY_CODE); - - assert.deepEqual(calendar.option('currentDate'), newDate, 'current date is correct'); - assert.deepEqual(calendar.option('value'), newDate, 'selected value has been changed'); - }); - - QUnit.test('home/end keys should focus the first/last available date in the current month', function(assert) { - const calendar = this.calendar; - - calendar.option({ - value: new Date(2020, 0, 15), - disabledDates: (args) => { - const date = args.date.getDate(); - return date <= 7 || date >= 23; - } - }); - const $viewsWrapper = $(calendar._$viewsWrapper); - - calendar.focus(); - - triggerKeydown($viewsWrapper, HOME_KEY_CODE); - assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 8)); - - triggerKeydown($viewsWrapper, END_KEY_CODE); - assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 22)); - }); - - QUnit.test('the focused date should always be in the [min, max] range', function(assert) { - const isAnimationOff = fx.off; - const animationSpy = sinon.spy(fx, 'animate'); - - try { - const calendar = this.calendar; - - calendar.option({ - value: new Date(2020, 0, 25), - disabledDates: (args) => { - const date = args.date.getDate(); - return date >= 5 && date <= 20; - }, - max: new Date(2020, 1, 20), - min: new Date(2020, 0, 25) - }); - const $viewsWrapper = $(calendar._$viewsWrapper); - - fx.off = false; - animationSpy.resetHistory(); - - calendar.focus(); - - triggerKeydown($viewsWrapper, PAGE_DOWN_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), new Date(2020, 1, 4), 'focused date is in the range (min, max)'); - assert.equal(animationSpy.callCount, 1, 'view has been changed'); - - triggerKeydown($viewsWrapper, PAGE_UP_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 25), 'focused date is in the range (min, max)'); - assert.equal(animationSpy.callCount, 2, 'view has been changed'); - - } finally { - fx.off = isAnimationOff; - animationSpy.restore(); - } - }); - - QUnit.test('up/downArrow should try focus the same date in the next/previous month when the column is disabled', function(assert) { - const isAnimationOff = fx.off; - const animationSpy = sinon.spy(fx, 'animate'); - - try { - const calendar = this.calendar; - - calendar.option({ - value: new Date(2020, 1, 5), - disabledDates: (args) => { - const day = args.date.getDay(); - const month = args.date.getMonth(); - const date = args.date.getDate(); - return month === 0 && day === 3 || month === 1 && day === 0 || month === 0 && day === 0 && date !== 5; - } - }); - const $viewsWrapper = $(calendar._$viewsWrapper); - - fx.off = false; - animationSpy.resetHistory(); - - calendar.focus(); - - triggerKeydown($viewsWrapper, UP_ARROW_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 5), 'the closest available date has been focused'); - assert.equal(animationSpy.callCount, 1, 'view has been changed'); - - triggerKeydown($viewsWrapper, DOWN_ARROW_KEY_CODE); - this.clock.tick(VIEW_ANIMATION_DURATION); - assert.deepEqual(calendar.option('currentDate'), new Date(2020, 1, 5), 'the closest available date has been focused'); - assert.equal(animationSpy.callCount, 2, 'view has been changed'); - - } finally { - fx.off = isAnimationOff; - animationSpy.restore(); - } - }); - - QUnit.test('calendar should properly initialize currentDate when initial value is disabled', function(assert) { - this.reinit({ - disabledDates: (args) => { - if(args.date.valueOf() === new Date(2010, 10, 10).valueOf()) { - return true; - } - }, - value: this.value, - focusStateEnabled: true - }); - - const calendar = this.calendar; - assert.ok(dateUtils.sameView(calendar.option('zoomLevel'), calendar.option('currentDate'), new Date(2010, 10, 11))); - }); - - QUnit.test('arrowUp/Down should focus cell on top/bottom', function(assert) { - const calendar = this.calendar; - - calendar.option({ - disabledDates: (args) => { - return args.date.getDate() === 15 || args.date.getDate() === 11; - }, - value: new Date(2020, 0, 16) - }); - const $viewsWrapper = $(calendar._$viewsWrapper); - - calendar.focus(); - - triggerKeydown($viewsWrapper, UP_ARROW_KEY_CODE); - assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 9), 'cell on top has been focused'); - - triggerKeydown($viewsWrapper, DOWN_ARROW_KEY_CODE); - assert.deepEqual(calendar.option('currentDate'), new Date(2020, 0, 16), 'cell on bottom has been focused'); - }); - - QUnit.test('value can be changed to disabled date', function(assert) { - const calendar = this.calendar; - const disabledDate = new Date(2010, 9, 10); - - calendar.option('value', disabledDate); - assert.strictEqual(calendar.option('value'), disabledDate, 'value is changed'); - }); - - QUnit.test('disabledDates argument contains correct component parameter', function(assert) { - const stub = sinon.stub(); - - this.reinit({ - disabledDates: stub, - value: this.value, - focusStateEnabled: true - }); - - const component = stub.lastCall.args[0].component; - assert.equal(component.NAME, 'dxCalendar', 'Correct component'); - }); - - QUnit.test('current day should be the same as selected on init when current month is disabled', function(assert) { - this.calendar.option('value', new Date(2010, 11, 10)); - - assert.deepEqual(this.calendar.option('currentDate'), this.calendar.option('value'), 'currentDate is the same as selected date'); - }); - - QUnit.test('current day should be set to the closest available date on init when there is available date on the current month', function(assert) { - this.calendar.option({ - value: new Date(2010, 10, 14), - disabledDates: (args) => { - return args.date.getDate() > 10 && args.date.getDate() <= 20; - } - }); - - assert.deepEqual(this.calendar.option('currentDate'), new Date(2010, 10, 10), 'currentDate is the closest available date'); - }); - - QUnit.test('It should not be possible to focus dates that are disabled using combination of disabledDates+min/max', function(assert) { - const calendar = this.calendar; - - calendar.option({ - value: new Date('2023/09/11'), - max: new Date('2023/09/16'), - min: new Date('2023/09/10'), - disabledDates: (d) => { - const day = d.date.getDay(); - - return d.view === 'month' && day === 0 || day === 6; - }, - }); - const $viewsWrapper = $(calendar._$viewsWrapper); - - calendar.focus(); - - triggerKeydown($viewsWrapper, LEFT_ARROW_KEY_CODE); - assert.deepEqual(calendar.option('currentDate'), new Date('2023/09/11'), 'left disabledDate is not focused'); - - calendar.option('value', new Date('2023/09/15')); - - triggerKeydown($viewsWrapper, RIGHT_ARROW_KEY_CODE); - assert.deepEqual(calendar.option('currentDate'), new Date('2023/09/15'), 'right disabledDate is not focused'); - }); -}); - - QUnit.module('Current date', { beforeEach: function() { fx.off = true; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/calendarViews.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/calendarViews.tests.js index 29428c9cd567..f00bb6857012 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/calendarViews.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/calendarViews.tests.js @@ -234,85 +234,21 @@ QUnit.module('MonthView', { }); [ - { currentDate: new Date(2012, 0, 1), expectedWeekNumbers: [52, 1, 2, 3, 4, 5] }, - { currentDate: new Date(2007, 0, 1), expectedWeekNumbers: [52, 1, 2, 3, 4, 5] }, - { currentDate: new Date(2007, 11, 31), expectedWeekNumbers: [48, 49, 50, 51, 52, 1] }, - { currentDate: new Date(2002, 11, 31), expectedWeekNumbers: [48, 49, 50, 51, 52, 1] }, - { currentDate: new Date(2008, 11, 29), expectedWeekNumbers: [48, 49, 50, 51, 52, 1] }, - { currentDate: new Date(2009, 11, 28), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2010, 11, 27), expectedWeekNumbers: [48, 49, 50, 51, 52, 1] }, - // 53 - { currentDate: new Date(2004, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2009, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2015, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2020, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, + { currentDate: new Date(2025, 11, 31), expectedWeekNumbers: [48, 49, 50, 51, 52, 1] }, { currentDate: new Date(2026, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2032, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2037, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2043, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2048, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2054, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2060, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2065, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2071, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2076, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2082, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2088, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2093, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2099, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2105, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2111, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2116, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2122, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2128, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2133, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2139, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2144, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2150, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2156, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2161, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2167, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2172, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2178, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2184, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2189, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2195, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2201, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2207, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2212, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2218, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2224, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2229, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2235, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2240, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2246, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2252, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2257, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2263, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2268, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2274, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2280, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2285, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2291, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2296, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2303, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2308, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2314, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2320, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2325, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2331, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2336, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2342, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2348, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2353, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2359, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2364, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2370, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2376, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2381, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2387, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2392, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, - { currentDate: new Date(2398, 11, 31), expectedWeekNumbers: [49, 50, 51, 52, 53, 1] }, + { currentDate: new Date(2027, 11, 31), expectedWeekNumbers: [48, 49, 50, 51, 52, 1] }, + { currentDate: new Date(2028, 11, 31), expectedWeekNumbers: [48, 49, 50, 51, 52, 1] }, + { currentDate: new Date(2029, 11, 31), expectedWeekNumbers: [48, 49, 50, 51, 52, 1] }, + { currentDate: new Date(2030, 11, 31), expectedWeekNumbers: [48, 49, 50, 51, 52, 1] }, + { currentDate: new Date(2033, 11, 31), expectedWeekNumbers: [48, 49, 50, 51, 52, 1] }, + + { currentDate: new Date(2025, 0, 1), expectedWeekNumbers: [1, 2, 3, 4, 5, 6] }, + { currentDate: new Date(2026, 0, 1), expectedWeekNumbers: [1, 2, 3, 4, 5, 6] }, + { currentDate: new Date(2027, 0, 1), expectedWeekNumbers: [53, 1, 2, 3, 4, 5] }, + { currentDate: new Date(2028, 0, 1), expectedWeekNumbers: [52, 1, 2, 3, 4, 5] }, + { currentDate: new Date(2029, 0, 1), expectedWeekNumbers: [52, 1, 2, 3, 4, 5] }, + { currentDate: new Date(2030, 0, 1), expectedWeekNumbers: [1, 2, 3, 4, 5, 6] }, + { currentDate: new Date(2034, 0, 1), expectedWeekNumbers: [52, 1, 2, 3, 4, 5] }, ].forEach(({ currentDate, expectedWeekNumbers }) => { QUnit.test(`iso week numbers of 'month' view with date: ${currentDate.toLocaleDateString()}`, function(assert) { this.reinit({ @@ -332,83 +268,6 @@ QUnit.module('MonthView', { assert.deepEqual(actualWeekNumbers, expectedWeekNumbers, 'week cell numbers'); }); }); - - [0, 1, 2, 3, 4, 5, 6].forEach((firstDayOfWeek) => { - QUnit.test(`count years with iso week 53, firstDayOfWeek: ${firstDayOfWeek}`, function(assert) { - let count = 0; - - for(let i = 0; i < 400; i++) { - this.reinit({ - value: new Date(2000 + i, 11, 31), - date: new Date(2000 + i, 11, 31), - showWeekNumbers: true, - weekNumberRule: 'firstFourDays', - firstDayOfWeek, - zoomLevel: 'month', - }); - - const $weekCell = this.$element.find(`.${CALENDAR_WEEK_NUMBER_CELL_CLASS}`); - const actualWeekNumbers = $weekCell.map(function() { - return Number($(this).text()); - }).get(); - - if(actualWeekNumbers.indexOf(53) !== -1) { - count++; - } - } - assert.equal(count, 71, 'Should have 71 years with iso week 53'); - }); - - QUnit.test(`count years with iso week 53, firstDayOfWeek: ${firstDayOfWeek}`, function(assert) { - let count = 0; - - for(let i = 0; i < 400; i++) { - this.reinit({ - value: new Date(2001 + i, 0, 1), - date: new Date(2001 + i, 0, 1), - showWeekNumbers: true, - weekNumberRule: 'firstFourDays', - firstDayOfWeek, - zoomLevel: 'month', - }); - - const $weekCell = this.$element.find(`.${CALENDAR_WEEK_NUMBER_CELL_CLASS}`); - const actualWeekNumbers = $weekCell.map(function() { - return Number($(this).text()); - }).get(); - - if(actualWeekNumbers.indexOf(53) !== -1 || (dateUtils.getWeekNumber(new Date(2000 + i, 11, 31), firstDayOfWeek, 'firstFourDays') === 53 && actualWeekNumbers[0] === 1)) { - count++; - } - } - assert.equal(count, 71, 'Should have 71 years with iso week 53'); - }); - - QUnit.test(`count years with iso week 52, firstDayOfWeek: ${firstDayOfWeek}`, function(assert) { - let count = 0; - - for(let i = 0; i < 400; i++) { - this.reinit({ - value: new Date(2000 + i, 11, 31), - date: new Date(2000 + i, 11, 31), - showWeekNumbers: true, - weekNumberRule: 'firstFourDays', - firstDayOfWeek, - zoomLevel: 'month', - }); - - const $weekCell = this.$element.find(`.${CALENDAR_WEEK_NUMBER_CELL_CLASS}`); - const actualWeekNumbers = $weekCell.map(function() { - return Number($(this).text()); - }).get(); - - if(actualWeekNumbers.indexOf(53) === -1) { - count++; - } - } - assert.equal(count, 329, 'Should have 329 years with iso week 52'); - }); - }); }); QUnit.module('YearView', { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dateRangeBox.options.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dateRangeBox.options.tests.js new file mode 100644 index 000000000000..e63aebb7141d --- /dev/null +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dateRangeBox.options.tests.js @@ -0,0 +1,667 @@ +import $ from 'jquery'; +import { isObject } from 'core/utils/type.js'; +import fx from 'common/core/animation/fx'; +import keyboardMock from '../../helpers/keyboardMock.js'; +import Popup from 'ui/popup/ui.popup'; +import 'ui/date_range_box'; + +QUnit.testStart(() => { + const markup = + '
'; + + $('#qunit-fixture').html(markup); +}); + +const getStartDateBoxInstance = dateRangeBoxInstance => dateRangeBoxInstance.getStartDateBox(); + +const getEndDateBoxInstance = dateRangeBoxInstance => dateRangeBoxInstance.getEndDateBox(); + + +const moduleConfig = { + beforeEach: function() { + fx.off = true; + + const init = (options) => { + this.$element = $('#dateRangeBox').dxDateRangeBox(options); + this.instance = this.$element.dxDateRangeBox('instance'); + + this.$startDateBox = $(this.instance.getStartDateBox().element()); + this.$endDateBox = $(this.instance.getEndDateBox().element()); + this.$startDateInput = $(this.instance.startDateField()); + this.$endDateInput = $(this.instance.endDateField()); + + this.getPopupContent = () => $(this.instance.content()); + this.getCalendar = () => this.instance.getStartDateBox()._strategy._widget; + }; + + this.reinit = (options) => { + this.instance.dispose(); + + init(options); + }; + + init({ + value: ['2023/01/05', '2023/02/14'], + multiView: true, + }); + }, + afterEach: function() { + fx.off = false; + } +}; + +QUnit.module('Option synchronization', moduleConfig, () => { + QUnit.test('StartDate option should get first item from value option on init', function(assert) { + const { value, startDate } = this.instance.option(); + + assert.strictEqual(startDate, value[0]); + }); + + QUnit.test('EndDate option should get second item from value option on init', function(assert) { + const { value, endDate } = this.instance.option(); + + assert.strictEqual(endDate, value[1]); + }); + + QUnit.test('Value option should get values from startDate/endDate options on init', function(assert) { + const startDate = '2023/01/01'; + const endDate = '2023/02/02'; + + this.reinit({ + startDate, endDate + }); + + const { value } = this.instance.option(); + + assert.strictEqual(value[0], startDate); + assert.strictEqual(value[1], endDate); + }); + + QUnit.test('Value should have higher priority if value option and startDate/endDate options are defined on init', function(assert) { + const startDate = '2023/01/01'; + const endDate = '2023/02/02'; + const value = ['2024/01/01', '2024/02/02']; + + this.reinit({ + startDate, endDate, value + }); + + assert.deepEqual(this.instance.option('value'), value, 'value is not changed'); + assert.strictEqual(this.instance.option('startDate'), value[0], 'startDate got date from value'); + assert.strictEqual(this.instance.option('endDate'), value[1]), 'endDate got date from value'; + }); + + QUnit.test('StartDate option should update value option on runtime change', function(assert) { + const startDate = '2023/01/05'; + + this.instance.option('startDate', startDate); + + assert.strictEqual(this.instance.option('value')[0], startDate); + }); + + QUnit.test('EndDate option should update value option on runtime change', function(assert) { + const endDate = '2023/01/15'; + + this.instance.option('endDate', endDate); + + assert.strictEqual(this.instance.option('value')[1], endDate); + }); + + QUnit.test('Value option should update startDate option on runtime change', function(assert) { + const value = ['2023/01/07', '2023/02/07']; + + this.instance.option('value', value); + + assert.strictEqual(this.instance.option('startDate'), value[0]); + }); + + QUnit.test('value option should update endDate option on runtime change', function(assert) { + const value = ['2023/01/07', '2023/02/07']; + + this.instance.option('value', value); + + assert.strictEqual(this.instance.option('endDate'), value[1]); + }); + + QUnit.test('StartDateBox opened option value should be change after change DateRangeBox option value', function(assert) { + const startDateBox = getStartDateBoxInstance(this.instance); + const endDateBox = getEndDateBoxInstance(this.instance); + + this.instance.option('opened', true); + + assert.strictEqual(startDateBox.option('opened'), true, 'startDateBox option was changed'); + assert.strictEqual(endDateBox.option('opened'), true, 'endDateBox option was not changed'); + + this.instance.option('opened', false); + + assert.strictEqual(startDateBox.option('opened'), false, 'startDateBox option was changed'); + assert.strictEqual(endDateBox.option('opened'), false, 'endDateBox option was not changed'); + }); + + QUnit.test('DateRangeBox startDateLabel, endDateLabel options should be passed in label option of startDateBox and endDateBox respectively', function(assert) { + const customStartDateLabel = 'Start Date Label'; + const customEndDateLabel = 'End Date Label'; + + this.reinit({ + startDateLabel: customStartDateLabel, + endDateLabel: customEndDateLabel, + }); + const startDateBox = getStartDateBoxInstance(this.instance); + const endDateBox = getEndDateBoxInstance(this.instance); + + assert.strictEqual(startDateBox.option('label'), customStartDateLabel, 'startDateBox label option has correct value'); + assert.strictEqual(endDateBox.option('label'), customEndDateLabel, 'endDateBox label option has correct value'); + }); + + QUnit.test('startDateBox label should be changed after change startDateLabel option value of dateRangeBox in runtime', function(assert) { + this.reinit({}); + + this.instance.option('startDateLabel', 'text'); + + assert.strictEqual(this.instance.getStartDateBox().option('label'), 'text', 'startDateBox label option value has been changed'); + }); + + QUnit.test('endDateBox label should be changed after change endDateLabel option value of dateRangeBox in runtime', function(assert) { + this.reinit({}); + + this.instance.option('endDateLabel', 'text'); + + assert.strictEqual(this.instance.getEndDateBox().option('label'), 'text', 'endDateLabel label option value has been changed'); + }); + + QUnit.test('startDateBox placeholder should be changed after change startDatePlaceholder option value of dateRangeBox in runtime', function(assert) { + this.reinit({}); + + this.instance.option('startDatePlaceholder', 'text'); + + assert.strictEqual(this.instance.getStartDateBox().option('placeholder'), 'text', 'startDateBox placeholder option value has been changed'); + }); + + QUnit.test('endDateBox placeholder should be changed after change endDatePlaceholder option value of dateRangeBox in runtime', function(assert) { + this.reinit({}); + + this.instance.option('endDatePlaceholder', 'text'); + + assert.strictEqual(this.instance.getEndDateBox().option('placeholder'), 'text', 'endDateBox placeholder option value has been changed'); + }); + + QUnit.test('DateRangeBox "startDateInputAttr", "endDateInputAttr" option values should be passed in "inputAttr" option of startDateBox and endDateBox after change in runtime respectively', function(assert) { + this.reinit({}); + + this.instance.option({ + startDateInputAttr: { id: 'startDateInput' }, + endDateInputAttr: { id: 'endDateInput' } + }); + + assert.deepEqual(this.instance.getStartDateBox().option('inputAttr'), { id: 'startDateInput' }, 'startDateBox inputAttr option has correct value'); + assert.deepEqual(this.instance.getEndDateBox().option('inputAttr'), { id: 'endDateInput' }, 'endDateBox inputAttr option has correct value'); + }); + + QUnit.test('DateRangeBox "startDateName", "endDateName" option values should be passed in "name" option of startDateBox and endDateBox after change in runtime respectively', function(assert) { + this.reinit({}); + + this.instance.option({ + startDateName: 'start_input', + endDateName: 'end_input', + }); + + assert.strictEqual(this.instance.getStartDateBox().option('name'), 'start_input', 'startDateBox name option has correct value'); + assert.strictEqual(this.instance.getEndDateBox().option('name'), 'end_input', 'endDateBox name option has correct value'); + }); + + QUnit.test('CalendarOptions should be passed to startDateBox on init', function(assert) { + this.reinit({ + calendarOptions: { + showWeekNumbers: true, + } + }); + + const startDateBox = getStartDateBoxInstance(this.instance); + + assert.strictEqual(startDateBox.option('calendarOptions.showWeekNumbers'), true); + }); + + QUnit.test('CalendarOptions should be passed to startDateBox on runtime change', function(assert) { + const startDateBox = getStartDateBoxInstance(this.instance); + + this.instance.option('calendarOptions', { showWeekNumbers: true }); + + assert.strictEqual(startDateBox.option('calendarOptions.showWeekNumbers'), true); + }); + + QUnit.test('CalendarOptions field should be correctly passed to startDateBox on runtime change', function(assert) { + const startDateBox = getStartDateBoxInstance(this.instance); + + this.instance.option('calendarOptions.showWeekNumbers', true); + + const calendarOptions = startDateBox.option('calendarOptions'); + + assert.ok(isObject(calendarOptions), 'option is object'); + assert.strictEqual(calendarOptions.showWeekNumbers, true); + }); + + QUnit.test('DropDownOptions should be passed to startDateBox on init', function(assert) { + this.reinit({ + dropDownOptions: { + width: 800, + } + }); + + const startDateBox = getStartDateBoxInstance(this.instance); + + assert.strictEqual(startDateBox.option('dropDownOptions.width'), 800); + }); + + QUnit.test('DropDownOptions should be passed to startDateBox on runtime change', function(assert) { + const startDateBox = getStartDateBoxInstance(this.instance); + + this.instance.option('dropDownOptions', { width: 800 }); + + assert.strictEqual(startDateBox.option('dropDownOptions.width'), 800); + }); + + QUnit.test('DropDownOptions field should be correctly passed to startDateBox on runtime change', function(assert) { + const startDateBox = getStartDateBoxInstance(this.instance); + + this.instance.option('dropDownOptions.hideOnOutsideClick', false); + + const dropDownOptions = startDateBox.option('dropDownOptions'); + + assert.ok(isObject(dropDownOptions), 'option is object'); + assert.strictEqual(dropDownOptions.hideOnOutsideClick, false); + }); + + [ + { + optionName: 'applyValueMode', + optionValue: 'useButtons' + }, { + optionName: 'applyButtonText', + optionValue: 'apply' + }, { + optionName: 'cancelButtonText', + optionValue: 'abort' + }, { + optionName: 'todayButtonText', + optionValue: 'now' + }, + // NOTE: disabledDates are not published now. Use calendarOptions.disabledDates + // { + // optionName: 'disabledDates', + // optionValue: [new Date('2023/04/27'), new Date('2023/04/28')] + // } + ].forEach(({ optionName, optionValue }) => { + QUnit.test(`${optionName} should be passed to startDateBox on init`, function(assert) { + this.reinit({ + [optionName]: optionValue + }); + + const startDateBox = getStartDateBoxInstance(this.instance); + + assert.deepEqual(startDateBox.option(optionName), optionValue); + }); + + QUnit.test(`${optionName} should be passed to startDateBox on runtime change`, function(assert) { + const startDateBox = getStartDateBoxInstance(this.instance); + + this.instance.option(optionName, optionValue); + + assert.deepEqual(startDateBox.option(optionName), optionValue); + }); + }); + + [ + { + optionName: 'isValid', + optionValue: false + } + ].forEach(({ optionName, optionValue }) => { + QUnit.test(`${optionName} should be passed to endDateBox on init`, function(assert) { + this.reinit({ + [optionName]: optionValue + }); + + const endDateBox = getEndDateBoxInstance(this.instance); + + assert.deepEqual(endDateBox.option(optionName), optionValue); + }); + + QUnit.test(`${optionName} should be passed to endDateBox on runtime change`, function(assert) { + const endDateBox = getEndDateBoxInstance(this.instance); + + this.instance.option(optionName, optionValue); + + assert.deepEqual(endDateBox.option(optionName), optionValue); + }); + }); + + [ + { + optionName: 'dateSerializationFormat', + optionValue: 'yyyy-MM-dd', + }, { + optionName: 'isValid', + optionValue: false, + }, { + optionName: 'height', + optionValue: 200, + }, { + optionName: 'useMaskBehavior', + optionValue: true, + }, { + optionName: 'valueChangeEvent', + optionValue: 'keyDown', + }, { + optionName: 'min', + optionValue: new Date('2023/03/01'), + }, { + optionName: 'max', + optionValue: new Date('2023/06/30'), + }, { + optionName: 'spellcheck', + optionValue: true, + }, { + optionName: 'pickerType', + optionValue: 'native' + }, { + optionName: 'acceptCustomValue', + optionValue: false + }, { + optionName: 'rtlEnabled', + optionValue: true + }, { + optionName: 'displayFormat', + optionValue: 'EEEE, d of MMM, yyyy' + }, + { + optionName: 'labelMode', + optionValue: 'floating' + }, + { + optionName: 'openOnFieldClick', + optionValue: false, + }, + { + optionName: 'focusStateEnabled', + optionValue: false, + }, + { + optionName: 'activeStateEnabled', + optionValue: false, + }, + { + optionName: 'hoverStateEnabled', + optionValue: false, + }, + { + optionName: 'tabIndex', + optionValue: 1, + }, + { + optionName: 'disabled', + optionValue: false, + } + ].forEach(({ optionName, optionValue }) => { + QUnit.test(`${optionName} should be passed to startDateBox and endDateBox on init`, function(assert) { + this.reinit({ + [optionName]: optionValue + }); + + assert.strictEqual(this.instance.getStartDateBox().option(optionName), optionValue); + assert.strictEqual(this.instance.getEndDateBox().option(optionName), optionValue); + }); + + QUnit.test(`${optionName} should be passed to startDateBox and endDateBox on runtime change`, function(assert) { + this.instance.option(optionName, optionValue); + + assert.strictEqual(this.instance.getStartDateBox().option(optionName), optionValue); + assert.strictEqual(this.instance.getEndDateBox().option(optionName), optionValue); + }); + }); + + ['startDateBox', 'endDateBox'].forEach((dateBoxName) => { + QUnit.test(`onValueChanged should have correct event on change value in ${dateBoxName}`, function(assert) { + const onValueChangedHandler = sinon.stub(); + + this.reinit({ + value: ['2023/02/23', '2023/03/24'], + valueChangeEvent: 'change', + onValueChanged: onValueChangedHandler, + }); + + const dateBox = dateBoxName === 'startDateBox' + ? getStartDateBoxInstance(this.instance) + : getEndDateBoxInstance(this.instance); + + const $input = $(dateBox.field()); + const keyboard = keyboardMock($input); + + keyboard + .caret({ start: 0, end: 1 }) + .type('1') + .change(); + + assert.strictEqual(onValueChangedHandler.callCount, 1, 'handler has been called once'); + assert.strictEqual(onValueChangedHandler.getCall(0).args[0].event.type, 'change', 'event is correct'); + + this.instance.option('value', [new Date(2021, 9, 17), new Date(2021, 9, 19)]); + assert.strictEqual(onValueChangedHandler.callCount, 2, 'handler has been called twice'); + assert.strictEqual(onValueChangedHandler.getCall(1).args[0].event, undefined, 'event has been cleared'); + }); + + QUnit.test(`value should change on keyup in ${dateBoxName} if valueChangeEvent is set to keyup on init`, function(assert) { + assert.expect(1); + + this.reinit({ + value: ['2023/02/23', '2023/03/24'], + valueChangeEvent: 'keyup', + onValueChanged: () => { + assert.ok(true); + } + }); + + const dateBox = dateBoxName === 'startDateBox' + ? getStartDateBoxInstance(this.instance) + : getEndDateBoxInstance(this.instance); + + const $input = $(dateBox.field()); + const keyboard = keyboardMock($input); + + keyboard + .caret({ start: 0, end: 1 }) + .type('1'); + }); + + QUnit.test(`value should change on keyup in ${dateBoxName} if valueChangeEvent is set to keyup on runtime`, function(assert) { + assert.expect(1); + + this.reinit({ + value: ['2023/02/23', '2023/03/24'], + onValueChanged: () => { + assert.ok(true); + } + }); + this.instance.option('valueChangeEvent', 'keyup'); + + const dateBox = dateBoxName === 'startDateBox' + ? getStartDateBoxInstance(this.instance) + : getEndDateBoxInstance(this.instance); + + const $input = $(dateBox.field()); + const keyboard = keyboardMock($input); + + keyboard + .caret({ start: 0, end: 1 }) + .type('1'); + }); + + QUnit.test('DateBoxes pickerType should be "calendar" if initial DateRangeBox pickerType is not "calendar" or "native"', function(assert) { + this.reinit({ + pickerType: 'rollers' + }); + + assert.strictEqual(this.instance.getStartDateBox().option('pickerType'), 'calendar'); + assert.strictEqual(this.instance.getEndDateBox().option('pickerType'), 'calendar'); + }); + + QUnit.test('DateBoxes pickerType should be "calendar" if DateRangeBox pickerType is not "calendar" or "native" on runtime change', function(assert) { + this.instance.option('pickerType', 'rollers'); + + assert.strictEqual(this.instance.getStartDateBox().option('pickerType'), 'calendar'); + assert.strictEqual(this.instance.getEndDateBox().option('pickerType'), 'calendar'); + }); + + QUnit.test('DateRangeBox startDateText and endDateText options should return text option of dateboxes correctly', function(assert) { + this.reinit({ + value: ['2021/09/17', '2021/09/24'], + }); + + assert.deepEqual(new Date(this.instance.option('startDateText')), new Date('2021/09/17')); + assert.deepEqual(new Date(this.instance.option('endDateText')), new Date('2021/09/24')); + assert.strictEqual(this.instance.option('startDateText'), this.instance.getStartDateBox().option('text')); + assert.strictEqual(this.instance.option('endDateText'), this.instance.getEndDateBox().option('text')); + }); + + QUnit.test('DateRangeBox startDateText and endDateText options should return text option of dateboxes correctly after change value in runtime', function(assert) { + this.reinit({}); + + this.instance.option('value', ['2021/09/17', '2021/09/24']); + + assert.deepEqual(new Date(this.instance.option('startDateText')), new Date('2021/09/17')); + assert.deepEqual(new Date(this.instance.option('endDateText')), new Date('2021/09/24')); + assert.strictEqual(this.instance.option('startDateText'), this.instance.getStartDateBox().option('text')); + assert.strictEqual(this.instance.option('endDateText'), this.instance.getEndDateBox().option('text')); + }); + }); + + QUnit.test('StartDateBox & EndDateBox popups should not be rendered by default', function(assert) { + assert.strictEqual(this.instance.getStartDateBox()._popup, undefined, 'startDateBox popup is not rendered by default'); + assert.strictEqual(this.instance.getEndDateBox()._popup, undefined, 'endDateBox popup is not rendered by default'); + }); + + QUnit.test('Only startDateBox should be rendered if deferRendering is false', function(assert) { + this.reinit({ + deferRendering: false, + }); + + assert.strictEqual(this.instance.getStartDateBox()._popup instanceof Popup, true, 'startDateBox popup is rendered'); + assert.strictEqual(this.instance.getEndDateBox()._popup, undefined, 'endDateBox popup is not rendered'); + }); + + QUnit.test('Only startDateBox should be rendered if deferRendering was changed in runtime', function(assert) { + this.reinit({}); + + assert.strictEqual(this.instance.getStartDateBox()._popup, undefined, 'startDateBox popup is not rendered by default'); + assert.strictEqual(this.instance.getEndDateBox()._popup, undefined, 'endDateBox popup is not rendered by default'); + + this.instance.option('deferRendering', false); + + assert.strictEqual(this.instance.getStartDateBox()._popup instanceof Popup, true, 'startDateBox popup is rendered'); + assert.strictEqual(this.instance.getEndDateBox()._popup, undefined, 'endDateBox popup is not rendered'); + }); + + QUnit.test('disabledDates argument should have component parameter with DateRangeBox instance if disabledDates are set as function', function(assert) { + const disabledDates = sinon.stub(); + + this.reinit({ + disabledDates, + opened: true + }); + + const componentField = disabledDates.lastCall.args[0].component; + assert.strictEqual(componentField.NAME, 'dxDateRangeBox', 'Correct component'); + }); +}); + +QUnit.module('calendarOptions', moduleConfig, () => { + // NOTE: commented props are restricted in docs: value is passed from DateRangeBox. + const calendarOptions = { + accessKey: 'b', + activeStateEnabled: false, + cellTemplate: () => {}, + // dateSerializationFormat: 'yyyy-MM-dd', + disabled: true, + disabledDates: () => {}, + elementAttr: {}, + firstDayOfWeek: 5, + focusStateEnabled: false, + height: 500, + hint: 'hint', + hoverStateEnabled: false, + isValid: false, + // max: new Date('5/5/2023'), + maxZoomLevel: 'year', + // min: new Date('5/5/2023'), + minZoomLevel: 'year', + name: 'name', + onDisposing: () => {}, + onInitialized: () => {}, + onOptionChanged: () => {}, + // onValueChanged: () => {}, + readOnly: true, + rtlEnabled: true, + showTodayButton: true, + showWeekNumbers: true, + // tabIndex: 1, + validationError: {}, + validationErrors: [{}], + validationMessageMode: 'always', + validationMessagePosition: 'top', + validationStatus: 'pending', + // value: [null, null], + visible: false, + weekNumberRule: 'fullWeek', + width: 500, + zoomLevel: 'year', + }; + + Object.entries(calendarOptions).forEach(([ name, value ]) => { + QUnit.test(`calendarOptions.${name} should be passed to the calendar on init`, function(assert) { + this.reinit({ + deferRendering: false, + [`calendarOptions.${name}`]: value + }); + + const calendar = this.getCalendar(); + + assert.deepEqual(calendar.option(name), value); + }); + + QUnit.test(`calendarOptions.${name} should be passed to the calendar on runtime change`, function(assert) { + this.instance.option({ + deferRendering: false, + [`calendarOptions.${name}`]: value + }); + + const calendar = this.getCalendar(); + + assert.deepEqual(calendar.option(name), value); + }); + }); + + QUnit.test('disabledDates should be passed to calendarOptions from dateRangeBox options when disabledDates option is array', function(assert) { + const dates = [new Date('07/1/2018')]; + this.reinit({ + deferRendering: false, + disabledDates: dates, + }); + + const calendar = this.getCalendar(); + + assert.deepEqual(calendar.option('disabledDates'), dates); + }); + + QUnit.test('disabledDates should be passed to calendarOptions from dateRangeBox options when disabledDates option is array and option changed at runtime', function(assert) { + const dates = [new Date('07/1/2018')]; + this.reinit({ + deferRendering: false, + }); + + this.instance.option('disabledDates', dates); + + const calendar = this.getCalendar(); + + assert.deepEqual(calendar.option('disabledDates'), dates); + }); +}); + diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dateRangeBox.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dateRangeBox.tests.js index 04eba1a66e6e..eb697b9cfca6 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dateRangeBox.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dateRangeBox.tests.js @@ -1,14 +1,11 @@ import $ from 'jquery'; import config from 'core/config'; -import devices from '__internal/core/m_devices'; import DateRangeBox from 'ui/date_range_box'; import DateBox from 'ui/date_box'; -import { isRenderer } from 'core/utils/type'; -import { isObject } from 'core/utils/type.js'; +import { isDefined, isRenderer } from 'core/utils/type'; import fx from 'common/core/animation/fx'; import hoverEvents from 'common/core/events/hover'; import keyboardMock from '../../helpers/keyboardMock.js'; -import Popup from 'ui/popup/ui.popup'; import localization from 'localization'; import 'ui/validator'; @@ -207,9 +204,12 @@ QUnit.module('DateRangeBox Initialization', moduleConfig, () => { width: undefined, }; - Object.entries(expectedOptions).forEach(([key, value]) => { - assert.deepEqual(this.instance.option(key), value, `${key} default value is correct`); + const actualOptions = {}; + Object.keys(expectedOptions).forEach((key) => { + actualOptions[key] = this.instance.option(key); }); + + assert.deepEqual(actualOptions, expectedOptions, 'default value is correct'); }); const expectedDateBoxOptions = { @@ -277,15 +277,22 @@ QUnit.module('DateRangeBox Initialization', moduleConfig, () => { }; const startDateBox = getStartDateBoxInstance(this.instance); - Object.entries(expectedOptions).forEach(([key, value]) => { + const actualOptions = {}; + Object.keys(expectedOptions).forEach((key) => { if(key === 'dropDownOptions') { Object.entries(startDateBox.option(key)).forEach(([dropDownOptionKey, dropDownOptionValue]) => { assert.deepEqual(dropDownOptionValue, startDateBox.option(`${key}.${dropDownOptionKey}`), `${key}.${dropDownOptionKey} default value is correct`); }); + actualOptions[key] = {}; + Object.keys(expectedOptions[key]).forEach((dropDownOptionKey) => { + actualOptions[key][dropDownOptionKey] = startDateBox.option(`${key}.${dropDownOptionKey}`); + }); } else { - assert.deepEqual(value, startDateBox.option(key), `${key} default value is correct`); + actualOptions[key] = startDateBox.option(key); } }); + + assert.deepEqual(actualOptions, expectedOptions, 'default value is correct'); }); QUnit.test('EndDateBox has expected defaults', function(assert) { @@ -301,9 +308,12 @@ QUnit.module('DateRangeBox Initialization', moduleConfig, () => { }; const endDateBox = getEndDateBoxInstance(this.instance); - Object.entries(expectedOptions).forEach(([key, value]) => { - assert.deepEqual(value, endDateBox.option(key), `${key} default value is correct`); + const actualOptions = {}; + Object.keys(expectedOptions).forEach((key) => { + actualOptions[key] = endDateBox.option(key); }); + + assert.deepEqual(actualOptions, expectedOptions, 'default value is correct'); }); }); @@ -347,9 +357,17 @@ QUnit.module('DateRangeBox Initialization', moduleConfig, () => { }; const startDateBox = getStartDateBoxInstance(this.instance); - Object.entries(initialDateRangeBoxOptions).forEach(([key]) => { - assert.deepEqual(startDateBox.option(key), expectedOptions[key], `${key} value is correct`); + const actualOptions = {}; + Object.keys(initialDateRangeBoxOptions).forEach((key) => { + actualOptions[key] = startDateBox.option(key); + expectedOptions[key] = isDefined(expectedOptions[key]) ? expectedOptions[key] : undefined; + }); + + Object.keys(expectedOptions).forEach((key) => { + actualOptions[key] = startDateBox.option(key); }); + + assert.deepEqual(actualOptions, expectedOptions, 'value is correct'); }); QUnit.test('EndDateBox has expected settings', function(assert) { @@ -360,7 +378,6 @@ QUnit.module('DateRangeBox Initialization', moduleConfig, () => { buttons: undefined, deferRendering: true, disabled: true, - earButton: false, focusStateEnabled: false, hoverStateEnabled: false, inputAttr: { id: 'endDateInput' }, @@ -373,9 +390,16 @@ QUnit.module('DateRangeBox Initialization', moduleConfig, () => { }; const endDateBox = getEndDateBoxInstance(this.instance); - Object.entries(initialDateRangeBoxOptions).forEach(([key]) => { - assert.deepEqual(endDateBox.option(key), expectedOptions[key], `${key} value is correct`); + const actualOptions = {}; + Object.keys(initialDateRangeBoxOptions).forEach((key) => { + actualOptions[key] = endDateBox.option(key); + expectedOptions[key] = isDefined(expectedOptions[key]) ? expectedOptions[key] : undefined; + }); + Object.keys(expectedOptions).forEach((key) => { + actualOptions[key] = endDateBox.option(key); }); + + assert.deepEqual(actualOptions, expectedOptions, 'value is correct'); }); }); }); @@ -1867,43 +1891,19 @@ QUnit.module('Popup integration', moduleConfig, () => { }); }); - QUnit.module('IOS', () => { - QUnit.test('Popup should not be closed after focus is moved to the end dateBox, especially on IOS', function(assert) { - this.instance.open(); - - const startDateBox = getStartDateBoxInstance(this.instance); - const $startDateBoxInput = $(startDateBox.field()); - $startDateBoxInput.trigger('focusin'); - - const endDateBox = getEndDateBoxInstance(this.instance); - const $endDateBoxInput = $(endDateBox.field()); - - $startDateBoxInput.trigger($.Event('focusout', { relatedTarget: $endDateBoxInput })); - - assert.strictEqual(this.instance.option('opened'), true, 'popup is not closed'); - }); - - QUnit.test('Popup should be closed after focus is moved to other editor using IOs special nextButton', function(assert) { - const isIOs = devices.current().platform === 'ios'; - if(!isIOs) { - assert.ok(true, 'test is actual only for ios'); - return; - } - - this.instance.open(); + QUnit.test('Popup should not be closed after focus is moved to the end dateBox', function(assert) { + this.instance.open(); - const startDateBox = getStartDateBoxInstance(this.instance); - const $startDateBoxInput = $(startDateBox.field()); - $startDateBoxInput.trigger('focusin'); + const startDateBox = getStartDateBoxInstance(this.instance); + const $startDateBoxInput = $(startDateBox.field()); + $startDateBoxInput.trigger('focusin'); - const otherDateRangeBox = $('#dateRangeBox2').dxDateRangeBox({}).dxDateRangeBox('instance'); - const otherStartDateBox = getStartDateBoxInstance(otherDateRangeBox); - const $otherStartDateBoxInput = $(otherStartDateBox.field()); + const endDateBox = getEndDateBoxInstance(this.instance); + const $endDateBoxInput = $(endDateBox.field()); - $startDateBoxInput.trigger($.Event('focusout', { relatedTarget: $otherStartDateBoxInput })); + $startDateBoxInput.trigger($.Event('focusout', { relatedTarget: $endDateBoxInput })); - assert.strictEqual(this.instance.option('opened'), false, 'popup is closed'); - }); + assert.strictEqual(this.instance.option('opened'), true, 'popup is not closed'); }); QUnit.module('popup can be opened by click on input after option change when popup was already rendered', () => { @@ -1938,529 +1938,6 @@ QUnit.module('Popup integration', moduleConfig, () => { }); }); -QUnit.module('Option synchronization', moduleConfig, () => { - QUnit.test('StartDate option should get first item from value option on init', function(assert) { - const { value, startDate } = this.instance.option(); - - assert.strictEqual(startDate, value[0]); - }); - - QUnit.test('EndDate option should get second item from value option on init', function(assert) { - const { value, endDate } = this.instance.option(); - - assert.strictEqual(endDate, value[1]); - }); - - QUnit.test('Value option should get values from startDate/endDate options on init', function(assert) { - const startDate = '2023/01/01'; - const endDate = '2023/02/02'; - - this.reinit({ - startDate, endDate - }); - - const { value } = this.instance.option(); - - assert.strictEqual(value[0], startDate); - assert.strictEqual(value[1], endDate); - }); - - QUnit.test('Value should have higher priority if value option and startDate/endDate options are defined on init', function(assert) { - const startDate = '2023/01/01'; - const endDate = '2023/02/02'; - const value = ['2024/01/01', '2024/02/02']; - - this.reinit({ - startDate, endDate, value - }); - - assert.deepEqual(this.instance.option('value'), value, 'value is not changed'); - assert.strictEqual(this.instance.option('startDate'), value[0], 'startDate got date from value'); - assert.strictEqual(this.instance.option('endDate'), value[1]), 'endDate got date from value'; - }); - - QUnit.test('StartDate option should update value option on runtime change', function(assert) { - const startDate = '2023/01/05'; - - this.instance.option('startDate', startDate); - - assert.strictEqual(this.instance.option('value')[0], startDate); - }); - - QUnit.test('EndDate option should update value option on runtime change', function(assert) { - const endDate = '2023/01/15'; - - this.instance.option('endDate', endDate); - - assert.strictEqual(this.instance.option('value')[1], endDate); - }); - - QUnit.test('Value option should update startDate option on runtime change', function(assert) { - const value = ['2023/01/07', '2023/02/07']; - - this.instance.option('value', value); - - assert.strictEqual(this.instance.option('startDate'), value[0]); - }); - - QUnit.test('value option should update endDate option on runtime change', function(assert) { - const value = ['2023/01/07', '2023/02/07']; - - this.instance.option('value', value); - - assert.strictEqual(this.instance.option('endDate'), value[1]); - }); - - QUnit.test('StartDateBox opened option value should be change after change DateRangeBox option value', function(assert) { - const startDateBox = getStartDateBoxInstance(this.instance); - const endDateBox = getEndDateBoxInstance(this.instance); - - this.instance.option('opened', true); - - assert.strictEqual(startDateBox.option('opened'), true, 'startDateBox option was changed'); - assert.strictEqual(endDateBox.option('opened'), true, 'endDateBox option was not changed'); - - this.instance.option('opened', false); - - assert.strictEqual(startDateBox.option('opened'), false, 'startDateBox option was changed'); - assert.strictEqual(endDateBox.option('opened'), false, 'endDateBox option was not changed'); - }); - - QUnit.test('DateRangeBox startDateLabel, endDateLabel options should be passed in label option of startDateBox and endDateBox respectively', function(assert) { - const customStartDateLabel = 'Start Date Label'; - const customEndDateLabel = 'End Date Label'; - - this.reinit({ - startDateLabel: customStartDateLabel, - endDateLabel: customEndDateLabel, - }); - const startDateBox = getStartDateBoxInstance(this.instance); - const endDateBox = getEndDateBoxInstance(this.instance); - - assert.strictEqual(startDateBox.option('label'), customStartDateLabel, 'startDateBox label option has correct value'); - assert.strictEqual(endDateBox.option('label'), customEndDateLabel, 'endDateBox label option has correct value'); - }); - - QUnit.test('startDateBox label should be changed after change startDateLabel option value of dateRangeBox in runtime', function(assert) { - this.reinit({}); - - this.instance.option('startDateLabel', 'text'); - - assert.strictEqual(this.instance.getStartDateBox().option('label'), 'text', 'startDateBox label option value has been changed'); - }); - - QUnit.test('endDateBox label should be changed after change endDateLabel option value of dateRangeBox in runtime', function(assert) { - this.reinit({}); - - this.instance.option('endDateLabel', 'text'); - - assert.strictEqual(this.instance.getEndDateBox().option('label'), 'text', 'endDateLabel label option value has been changed'); - }); - - QUnit.test('startDateBox placeholder should be changed after change startDatePlaceholder option value of dateRangeBox in runtime', function(assert) { - this.reinit({}); - - this.instance.option('startDatePlaceholder', 'text'); - - assert.strictEqual(this.instance.getStartDateBox().option('placeholder'), 'text', 'startDateBox placeholder option value has been changed'); - }); - - QUnit.test('endDateBox placeholder should be changed after change endDatePlaceholder option value of dateRangeBox in runtime', function(assert) { - this.reinit({}); - - this.instance.option('endDatePlaceholder', 'text'); - - assert.strictEqual(this.instance.getEndDateBox().option('placeholder'), 'text', 'endDateBox placeholder option value has been changed'); - }); - - QUnit.test('DateRangeBox "startDateInputAttr", "endDateInputAttr" option values should be passed in "inputAttr" option of startDateBox and endDateBox after change in runtime respectively', function(assert) { - this.reinit({}); - - this.instance.option({ - startDateInputAttr: { id: 'startDateInput' }, - endDateInputAttr: { id: 'endDateInput' } - }); - - assert.deepEqual(this.instance.getStartDateBox().option('inputAttr'), { id: 'startDateInput' }, 'startDateBox inputAttr option has correct value'); - assert.deepEqual(this.instance.getEndDateBox().option('inputAttr'), { id: 'endDateInput' }, 'endDateBox inputAttr option has correct value'); - }); - - QUnit.test('DateRangeBox "startDateName", "endDateName" option values should be passed in "name" option of startDateBox and endDateBox after change in runtime respectively', function(assert) { - this.reinit({}); - - this.instance.option({ - startDateName: 'start_input', - endDateName: 'end_input', - }); - - assert.strictEqual(this.instance.getStartDateBox().option('name'), 'start_input', 'startDateBox name option has correct value'); - assert.strictEqual(this.instance.getEndDateBox().option('name'), 'end_input', 'endDateBox name option has correct value'); - }); - - QUnit.test('CalendarOptions should be passed to startDateBox on init', function(assert) { - this.reinit({ - calendarOptions: { - showWeekNumbers: true, - } - }); - - const startDateBox = getStartDateBoxInstance(this.instance); - - assert.strictEqual(startDateBox.option('calendarOptions.showWeekNumbers'), true); - }); - - QUnit.test('CalendarOptions should be passed to startDateBox on runtime change', function(assert) { - const startDateBox = getStartDateBoxInstance(this.instance); - - this.instance.option('calendarOptions', { showWeekNumbers: true }); - - assert.strictEqual(startDateBox.option('calendarOptions.showWeekNumbers'), true); - }); - - QUnit.test('CalendarOptions field should be correctly passed to startDateBox on runtime change', function(assert) { - const startDateBox = getStartDateBoxInstance(this.instance); - - this.instance.option('calendarOptions.showWeekNumbers', true); - - const calendarOptions = startDateBox.option('calendarOptions'); - - assert.ok(isObject(calendarOptions), 'option is object'); - assert.strictEqual(calendarOptions.showWeekNumbers, true); - }); - - QUnit.test('DropDownOptions should be passed to startDateBox on init', function(assert) { - this.reinit({ - dropDownOptions: { - width: 800, - } - }); - - const startDateBox = getStartDateBoxInstance(this.instance); - - assert.strictEqual(startDateBox.option('dropDownOptions.width'), 800); - }); - - QUnit.test('DropDownOptions should be passed to startDateBox on runtime change', function(assert) { - const startDateBox = getStartDateBoxInstance(this.instance); - - this.instance.option('dropDownOptions', { width: 800 }); - - assert.strictEqual(startDateBox.option('dropDownOptions.width'), 800); - }); - - QUnit.test('DropDownOptions field should be correctly passed to startDateBox on runtime change', function(assert) { - const startDateBox = getStartDateBoxInstance(this.instance); - - this.instance.option('dropDownOptions.hideOnOutsideClick', false); - - const dropDownOptions = startDateBox.option('dropDownOptions'); - - assert.ok(isObject(dropDownOptions), 'option is object'); - assert.strictEqual(dropDownOptions.hideOnOutsideClick, false); - }); - - [ - { - optionName: 'applyValueMode', - optionValue: 'useButtons' - }, { - optionName: 'applyButtonText', - optionValue: 'apply' - }, { - optionName: 'cancelButtonText', - optionValue: 'abort' - }, { - optionName: 'todayButtonText', - optionValue: 'now' - }, - // NOTE: disabledDates are not published now. Use calendarOptions.disabledDates - // { - // optionName: 'disabledDates', - // optionValue: [new Date('2023/04/27'), new Date('2023/04/28')] - // } - ].forEach(({ optionName, optionValue }) => { - QUnit.test(`${optionName} should be passed to startDateBox on init`, function(assert) { - this.reinit({ - [optionName]: optionValue - }); - - const startDateBox = getStartDateBoxInstance(this.instance); - - assert.deepEqual(startDateBox.option(optionName), optionValue); - }); - - QUnit.test(`${optionName} should be passed to startDateBox on runtime change`, function(assert) { - const startDateBox = getStartDateBoxInstance(this.instance); - - this.instance.option(optionName, optionValue); - - assert.deepEqual(startDateBox.option(optionName), optionValue); - }); - }); - - [ - { - optionName: 'isValid', - optionValue: false - } - ].forEach(({ optionName, optionValue }) => { - QUnit.test(`${optionName} should be passed to endDateBox on init`, function(assert) { - this.reinit({ - [optionName]: optionValue - }); - - const endDateBox = getEndDateBoxInstance(this.instance); - - assert.deepEqual(endDateBox.option(optionName), optionValue); - }); - - QUnit.test(`${optionName} should be passed to endDateBox on runtime change`, function(assert) { - const endDateBox = getEndDateBoxInstance(this.instance); - - this.instance.option(optionName, optionValue); - - assert.deepEqual(endDateBox.option(optionName), optionValue); - }); - }); - - [ - { - optionName: 'dateSerializationFormat', - optionValue: 'yyyy-MM-dd', - }, { - optionName: 'isValid', - optionValue: false, - }, { - optionName: 'height', - optionValue: 200, - }, { - optionName: 'useMaskBehavior', - optionValue: true, - }, { - optionName: 'valueChangeEvent', - optionValue: 'keyDown', - }, { - optionName: 'min', - optionValue: new Date('2023/03/01'), - }, { - optionName: 'max', - optionValue: new Date('2023/06/30'), - }, { - optionName: 'spellcheck', - optionValue: true, - }, { - optionName: 'pickerType', - optionValue: 'native' - }, { - optionName: 'acceptCustomValue', - optionValue: false - }, { - optionName: 'rtlEnabled', - optionValue: true - }, { - optionName: 'displayFormat', - optionValue: 'EEEE, d of MMM, yyyy' - }, - { - optionName: 'labelMode', - optionValue: 'floating' - }, - { - optionName: 'openOnFieldClick', - optionValue: false, - }, - { - optionName: 'focusStateEnabled', - optionValue: false, - }, - { - optionName: 'activeStateEnabled', - optionValue: false, - }, - { - optionName: 'hoverStateEnabled', - optionValue: false, - }, - { - optionName: 'tabIndex', - optionValue: 1, - }, - { - optionName: 'disabled', - optionValue: false, - } - ].forEach(({ optionName, optionValue }) => { - QUnit.test(`${optionName} should be passed to startDateBox and endDateBox on init`, function(assert) { - this.reinit({ - [optionName]: optionValue - }); - - assert.strictEqual(this.instance.getStartDateBox().option(optionName), optionValue); - assert.strictEqual(this.instance.getEndDateBox().option(optionName), optionValue); - }); - - QUnit.test(`${optionName} should be passed to startDateBox and endDateBox on runtime change`, function(assert) { - this.instance.option(optionName, optionValue); - - assert.strictEqual(this.instance.getStartDateBox().option(optionName), optionValue); - assert.strictEqual(this.instance.getEndDateBox().option(optionName), optionValue); - }); - }); - - ['startDateBox', 'endDateBox'].forEach((dateBoxName) => { - QUnit.test(`onValueChanged should have correct event on change value in ${dateBoxName}`, function(assert) { - const onValueChangedHandler = sinon.stub(); - - this.reinit({ - value: ['2023/02/23', '2023/03/24'], - valueChangeEvent: 'change', - onValueChanged: onValueChangedHandler, - }); - - const dateBox = dateBoxName === 'startDateBox' - ? getStartDateBoxInstance(this.instance) - : getEndDateBoxInstance(this.instance); - - const $input = $(dateBox.field()); - const keyboard = keyboardMock($input); - - keyboard - .caret({ start: 0, end: 1 }) - .type('1') - .change(); - - assert.strictEqual(onValueChangedHandler.callCount, 1, 'handler has been called once'); - assert.strictEqual(onValueChangedHandler.getCall(0).args[0].event.type, 'change', 'event is correct'); - - this.instance.option('value', [new Date(2021, 9, 17), new Date(2021, 9, 19)]); - assert.strictEqual(onValueChangedHandler.callCount, 2, 'handler has been called twice'); - assert.strictEqual(onValueChangedHandler.getCall(1).args[0].event, undefined, 'event has been cleared'); - }); - - QUnit.test(`value should change on keyup in ${dateBoxName} if valueChangeEvent is set to keyup on init`, function(assert) { - assert.expect(1); - - this.reinit({ - value: ['2023/02/23', '2023/03/24'], - valueChangeEvent: 'keyup', - onValueChanged: () => { - assert.ok(true); - } - }); - - const dateBox = dateBoxName === 'startDateBox' - ? getStartDateBoxInstance(this.instance) - : getEndDateBoxInstance(this.instance); - - const $input = $(dateBox.field()); - const keyboard = keyboardMock($input); - - keyboard - .caret({ start: 0, end: 1 }) - .type('1'); - }); - - QUnit.test(`value should change on keyup in ${dateBoxName} if valueChangeEvent is set to keyup on runtime`, function(assert) { - assert.expect(1); - - this.reinit({ - value: ['2023/02/23', '2023/03/24'], - onValueChanged: () => { - assert.ok(true); - } - }); - this.instance.option('valueChangeEvent', 'keyup'); - - const dateBox = dateBoxName === 'startDateBox' - ? getStartDateBoxInstance(this.instance) - : getEndDateBoxInstance(this.instance); - - const $input = $(dateBox.field()); - const keyboard = keyboardMock($input); - - keyboard - .caret({ start: 0, end: 1 }) - .type('1'); - }); - - QUnit.test('DateBoxes pickerType should be "calendar" if initial DateRangeBox pickerType is not "calendar" or "native"', function(assert) { - this.reinit({ - pickerType: 'rollers' - }); - - assert.strictEqual(this.instance.getStartDateBox().option('pickerType'), 'calendar'); - assert.strictEqual(this.instance.getEndDateBox().option('pickerType'), 'calendar'); - }); - - QUnit.test('DateBoxes pickerType should be "calendar" if DateRangeBox pickerType is not "calendar" or "native" on runtime change', function(assert) { - this.instance.option('pickerType', 'rollers'); - - assert.strictEqual(this.instance.getStartDateBox().option('pickerType'), 'calendar'); - assert.strictEqual(this.instance.getEndDateBox().option('pickerType'), 'calendar'); - }); - - QUnit.test('DateRangeBox startDateText and endDateText options should return text option of dateboxes correctly', function(assert) { - this.reinit({ - value: ['2021/09/17', '2021/09/24'], - }); - - assert.deepEqual(new Date(this.instance.option('startDateText')), new Date('2021/09/17')); - assert.deepEqual(new Date(this.instance.option('endDateText')), new Date('2021/09/24')); - assert.strictEqual(this.instance.option('startDateText'), this.instance.getStartDateBox().option('text')); - assert.strictEqual(this.instance.option('endDateText'), this.instance.getEndDateBox().option('text')); - }); - - QUnit.test('DateRangeBox startDateText and endDateText options should return text option of dateboxes correctly after change value in runtime', function(assert) { - this.reinit({}); - - this.instance.option('value', ['2021/09/17', '2021/09/24']), - - assert.deepEqual(new Date(this.instance.option('startDateText')), new Date('2021/09/17')); - assert.deepEqual(new Date(this.instance.option('endDateText')), new Date('2021/09/24')); - assert.strictEqual(this.instance.option('startDateText'), this.instance.getStartDateBox().option('text')); - assert.strictEqual(this.instance.option('endDateText'), this.instance.getEndDateBox().option('text')); - }); - }); - - QUnit.test('StartDateBox & EndDateBox popups should not be rendered by default', function(assert) { - assert.strictEqual(this.instance.getStartDateBox()._popup, undefined, 'startDateBox popup is not rendered by default'); - assert.strictEqual(this.instance.getEndDateBox()._popup, undefined, 'endDateBox popup is not rendered by default'); - }); - - QUnit.test('Only startDateBox should be rendered if deferRendering is false', function(assert) { - this.reinit({ - deferRendering: false, - }); - - assert.strictEqual(this.instance.getStartDateBox()._popup instanceof Popup, true, 'startDateBox popup is rendered'); - assert.strictEqual(this.instance.getEndDateBox()._popup, undefined, 'endDateBox popup is not rendered'); - }); - - QUnit.test('Only startDateBox should be rendered if deferRendering was changed in runtime', function(assert) { - this.reinit({}); - - assert.strictEqual(this.instance.getStartDateBox()._popup, undefined, 'startDateBox popup is not rendered by default'); - assert.strictEqual(this.instance.getEndDateBox()._popup, undefined, 'endDateBox popup is not rendered by default'); - - this.instance.option('deferRendering', false); - - assert.strictEqual(this.instance.getStartDateBox()._popup instanceof Popup, true, 'startDateBox popup is rendered'); - assert.strictEqual(this.instance.getEndDateBox()._popup, undefined, 'endDateBox popup is not rendered'); - }); - - QUnit.test('disabledDates argument should have component parameter with DateRangeBox instance if disabledDates are set as function', function(assert) { - const disabledDates = sinon.stub(); - - this.reinit({ - disabledDates, - opened: true - }); - - const componentField = disabledDates.lastCall.args[0].component; - assert.strictEqual(componentField.NAME, 'dxDateRangeBox', 'Correct component'); - }); -}); - QUnit.module('Dimensions', moduleConfig, () => { [undefined, 700].forEach((width) => { QUnit.test(`startDateBox and endDateBox should have equal width (dateRangeBox width = ${width})`, function(assert) { @@ -3870,98 +3347,6 @@ QUnit.module('localization', moduleConfig, () => { }); }); -QUnit.module('calendarOptions', moduleConfig, () => { - // NOTE: commented props are restricted in docs: value is passed from DateRangeBox. - const calendarOptions = { - accessKey: 'b', - activeStateEnabled: false, - cellTemplate: () => {}, - // dateSerializationFormat: 'yyyy-MM-dd', - disabled: true, - disabledDates: () => {}, - elementAttr: {}, - firstDayOfWeek: 5, - focusStateEnabled: false, - height: 500, - hint: 'hint', - hoverStateEnabled: false, - isValid: false, - // max: new Date('5/5/2023'), - maxZoomLevel: 'year', - // min: new Date('5/5/2023'), - minZoomLevel: 'year', - name: 'name', - onDisposing: () => {}, - onInitialized: () => {}, - onOptionChanged: () => {}, - // onValueChanged: () => {}, - readOnly: true, - rtlEnabled: true, - showTodayButton: true, - showWeekNumbers: true, - // tabIndex: 1, - validationError: {}, - validationErrors: [{}], - validationMessageMode: 'always', - validationMessagePosition: 'top', - validationStatus: 'pending', - // value: [null, null], - visible: false, - weekNumberRule: 'fullWeek', - width: 500, - zoomLevel: 'year', - }; - - Object.entries(calendarOptions).forEach(([ name, value ]) => { - QUnit.test(`calendarOptions.${name} should be passed to the calendar on init`, function(assert) { - this.reinit({ - deferRendering: false, - [`calendarOptions.${name}`]: value - }); - - const calendar = this.getCalendar(); - - assert.deepEqual(calendar.option(name), value); - }); - - QUnit.test(`calendarOptions.${name} should be passed to the calendar on runtime change`, function(assert) { - this.instance.option({ - deferRendering: false, - [`calendarOptions.${name}`]: value - }); - - const calendar = this.getCalendar(); - - assert.deepEqual(calendar.option(name), value); - }); - }); - - QUnit.test('disabledDates should be passed to calendarOptions from dateRangeBox options when disabledDates option is array', function(assert) { - const dates = [new Date('07/1/2018')]; - this.reinit({ - deferRendering: false, - disabledDates: dates, - }); - - const calendar = this.getCalendar(); - - assert.deepEqual(calendar.option('disabledDates'), dates); - }); - - QUnit.test('disabledDates should be passed to calendarOptions from dateRangeBox options when disabledDates option is array and option changed at runtime', function(assert) { - const dates = [new Date('07/1/2018')]; - this.reinit({ - deferRendering: false, - }); - - this.instance.option('disabledDates', dates); - - const calendar = this.getCalendar(); - - assert.deepEqual(calendar.option('disabledDates'), dates); - }); -}); - QUnit.module('Aria accessibility', { beforeEach: function() { moduleConfig.beforeEach.apply(this); @@ -4176,93 +3561,91 @@ QUnit.module('isDirty', moduleConfig, () => { }); }); -if(devices.real().deviceType === 'desktop') { - QUnit.module('Keyboard navigation', moduleConfig, () => { - const toolbarItems = [{ - widget: 'dxButton', - toolbar: 'top', - location: 'before', - options: { - text: 'Button', - }, +QUnit.module('Keyboard navigation', moduleConfig, () => { + const toolbarItems = [{ + widget: 'dxButton', + toolbar: 'top', + location: 'before', + options: { + text: 'Button', }, - { - widget: 'dxTextBox', - toolbar: 'bottom', - location: 'before', - options: { - text: 'Text box', - }, - }]; - - QUnit.test('pressing tab should set focus on previous month button in calendar', function(assert) { - this.reinit({ - opened: true, - applyValueMode: 'useButtons', - }); - - this.$endDateInput - .focus() - .trigger($.Event('keydown', { - key: 'Tab', - })); + }, + { + widget: 'dxTextBox', + toolbar: 'bottom', + location: 'before', + options: { + text: 'Text box', + }, + }]; - const $prevButton = this.getPopupContent().parent().find(`.${CALENDAR_NAVIGATOR_PREVIOUS_VIEW_CLASS}`); - assert.ok($prevButton.hasClass(STATE_FOCUSED_CLASS)); + QUnit.test('pressing tab should set focus on previous month button in calendar', function(assert) { + this.reinit({ + opened: true, + applyValueMode: 'useButtons', }); - QUnit.test('pressing tab + shift should set focus on cancel button in popup', function(assert) { - this.reinit({ - opened: true, - applyValueMode: 'useButtons', - }); - this.$startDateInput - .focus() - .trigger($.Event('keydown', { - key: 'Tab', - shiftKey: true - })); + this.$endDateInput + .focus() + .trigger($.Event('keydown', { + key: 'Tab', + })); + + const $prevButton = this.getPopupContent().parent().find(`.${CALENDAR_NAVIGATOR_PREVIOUS_VIEW_CLASS}`); + assert.ok($prevButton.hasClass(STATE_FOCUSED_CLASS)); + }); - const $cancelButton = this.getPopupContent().parent().find(CANCEL_BUTTON_SELECTOR); - assert.ok($cancelButton.hasClass(STATE_FOCUSED_CLASS)); + QUnit.test('pressing tab + shift should set focus on cancel button in popup', function(assert) { + this.reinit({ + opened: true, + applyValueMode: 'useButtons', }); + this.$startDateInput + .focus() + .trigger($.Event('keydown', { + key: 'Tab', + shiftKey: true + })); - QUnit.test('pressing tab should set focus on first item in popup with custom items', function(assert) { - this.reinit({ - opened: true, - applyValueMode: 'useButtons', - dropDownOptions: { - toolbarItems, - }, - }); - this.$endDateInput - .focus() - .trigger($.Event('keydown', { - key: 'Tab', - })); + const $cancelButton = this.getPopupContent().parent().find(CANCEL_BUTTON_SELECTOR); + assert.ok($cancelButton.hasClass(STATE_FOCUSED_CLASS)); + }); - const $firstItem = this.getPopupContent().parent().find(BUTTON_SELECTOR); - assert.ok($firstItem.hasClass(STATE_FOCUSED_CLASS)); + QUnit.test('pressing tab should set focus on first item in popup with custom items', function(assert) { + this.reinit({ + opened: true, + applyValueMode: 'useButtons', + dropDownOptions: { + toolbarItems, + }, }); + this.$endDateInput + .focus() + .trigger($.Event('keydown', { + key: 'Tab', + })); - QUnit.test('pressing tab + shift should set focus on last item in popup with custom items', function(assert) { - this.reinit({ - opened: true, - applyValueMode: 'useButtons', - dropDownOptions: { - toolbarItems, - }, - }); - this.$startDateInput - .focus() - .trigger($.Event('keydown', { - key: 'Tab', - shiftKey: true - })); + const $firstItem = this.getPopupContent().parent().find(BUTTON_SELECTOR); + assert.ok($firstItem.hasClass(STATE_FOCUSED_CLASS)); + }); - const $lastItem = this.getPopupContent().parent().find(TEXTBOX_SELECTOR); - assert.ok($lastItem.hasClass(STATE_FOCUSED_CLASS)); + QUnit.test('pressing tab + shift should set focus on last item in popup with custom items', function(assert) { + this.reinit({ + opened: true, + applyValueMode: 'useButtons', + dropDownOptions: { + toolbarItems, + }, }); + this.$startDateInput + .focus() + .trigger($.Event('keydown', { + key: 'Tab', + shiftKey: true + })); + + const $lastItem = this.getPopupContent().parent().find(TEXTBOX_SELECTOR); + assert.ok($lastItem.hasClass(STATE_FOCUSED_CLASS)); }); -} +}); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/datebox.integration.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/datebox.integration.tests.js new file mode 100644 index 000000000000..a85b8a8a71fd --- /dev/null +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/datebox.integration.tests.js @@ -0,0 +1,3116 @@ +import '../../helpers/noIntl.js'; +import $ from 'jquery'; +import Box from 'ui/box'; +import DateBox from 'ui/date_box'; +import dateLocalization from 'common/core/localization/date'; +import dateUtils from 'core/utils/date'; +import devices from '__internal/core/m_devices'; +import fx from 'common/core/animation/fx'; +import keyboardMock from '../../helpers/keyboardMock.js'; +import { getActiveElement } from '../../helpers/shadowDom.js'; +import pointerMock from '../../helpers/pointerMock.js'; +import uiDateUtils from '__internal/ui/date_box/m_date_utils'; +import { noop } from 'core/utils/common'; +import { logger } from 'core/utils/console'; + +import '../../helpers/calendarFixtures.js'; + +import 'ui/validator'; +import 'generic_light.css!'; +import { implementationsMap } from 'core/utils/size'; + +QUnit.testStart(() => { + const markup = + '
\ +
\ +
\ +
\ +
{ + return instance._strategy._widget; +}; + +const clearInput = ($element, keyboard) => { + const textLength = $element.val().length; + keyboard + .caret({ start: 0, end: textLength }) + .press('backspace'); +}; + +QUnit.module('datebox and calendar integration', () => { + QUnit.test('default', function(assert) { + const $element = $('#dateBox').dxDateBox({ pickerType: 'calendar' }); + + assert.ok($element.outerWidth() > 0, 'outer width of the element must be more than zero'); + }); + + QUnit.test('change width', function(assert) { + const $element = $('#dateBox').dxDateBox({ pickerType: 'calendar' }); + const instance = $element.dxDateBox('instance'); + const customWidth = 258; + + instance.option('width', customWidth); + + assert.strictEqual($element.outerWidth(), customWidth, 'outer width of the element must be equal to custom width'); + }); + + QUnit.test('change input value should change calendar value', function(assert) { + const $dateBox = $('#dateBox').dxDateBox({ + pickerType: 'calendar', + type: 'date', + value: new Date(2016, 1, 25) + }); + $($dateBox.find(`.${DROP_DOWN_BUTTON_CLASS}`)).trigger('dxclick'); + + const dateBox = $dateBox.dxDateBox('instance'); + const calendar = $('.dx-calendar').dxCalendar('instance'); + + const $input = $dateBox.find('.' + TEXTEDITOR_INPUT_CLASS); + let dateString = $input.val(); + dateString = dateString.slice(0, -1) + String(new Date().getYear() - 1).slice(-1); + + $input.val(''); + keyboardMock($input).type(dateString); + $($input).trigger('change'); + + assert.deepEqual(calendar.option('value'), dateBox.option('value'), 'datebox value and calendar value are equal'); + assert.strictEqual(dateBox.option('isValid'), true, 'Editor should be marked as true'); + assert.strictEqual(dateBox.option('validationError'), null, 'No validation error should be specified for valid input'); + }); + + QUnit.test('wrong value in input should mark datebox as invalid', function(assert) { + const $dateBox = $('#dateBox').dxDateBox({ + value: null, + type: 'date', + pickerType: 'calendar' + }); + + const dateBox = $dateBox.dxDateBox('instance'); + const $input = $dateBox.find('.' + TEXTEDITOR_INPUT_CLASS); + + keyboardMock($input).type('blabla'); + $($input).trigger('change'); + assert.equal($input.val(), 'blabla', 'input value should not be erased'); + assert.strictEqual(dateBox.option('value'), null, 'Editor\'s value should be reset'); + assert.strictEqual(dateBox.option('isValid'), false, 'Editor should be marked as invalid'); + const validationError = dateBox.option('validationError'); + assert.ok(validationError, 'Validation error should be specified'); + assert.ok(validationError.editorSpecific, 'editorSpecific flag should be added'); + }); + + QUnit.test('datebox should not be revalidated when readOnly option changed', function(assert) { + const dateBox = $('#dateBox').dxDateBox({ + readOnly: false + }).dxValidator({ + validationRules: [{ + type: 'required', + message: 'Date of birth is required' + }] + }).dxDateBox('instance'); + + dateBox.option('readOnly', true); + dateBox.option('readOnly', false); + + assert.ok(dateBox.option('isValid'), 'dateBox is valid'); + assert.notOk($('#dateBox').hasClass('dx-invalid'), 'dateBox is not marked as invalid'); + }); + + QUnit.test('wrong value in input should mark time datebox as invalid', function(assert) { + const $dateBox = $('#dateBox').dxDateBox({ + value: null, + type: 'time', + pickerType: 'calendar' + }); + + const dateBox = $dateBox.dxDateBox('instance'); + const $input = $dateBox.find('.' + TEXTEDITOR_INPUT_CLASS); + + keyboardMock($input).type('blabla'); + $($input).trigger('change'); + + assert.equal($input.val(), 'blabla', 'input value should not be erased'); + assert.strictEqual(dateBox.option('value'), null, 'Editor\'s value should be reset'); + assert.strictEqual(dateBox.option('isValid'), false, 'Editor should be marked as invalid'); + const validationError = dateBox.option('validationError'); + assert.ok(validationError, 'Validation error should be specified'); + assert.ok(validationError.editorSpecific, 'editorSpecific flag should be added'); + }); + + QUnit.test('wrong value in input should mark pre-filled datebox as invalid', function(assert) { + const value = new Date(2013, 2, 2); + + const $dateBox = $('#dateBox').dxDateBox({ + type: 'date', + value: new Date(value), + pickerType: 'calendar' + }); + + const dateBox = $dateBox.dxDateBox('instance'); + const $input = $dateBox.find('.' + TEXTEDITOR_INPUT_CLASS); + + $input.val(''); + keyboardMock($input).type('blabla'); + $($input).trigger('change'); + + assert.equal($input.val(), 'blabla', 'input value should not be erased'); + assert.deepEqual(dateBox.option('value'), value, 'Editor\'s value should not be changed'); + assert.strictEqual(dateBox.option('isValid'), false, 'Editor should be marked as invalid'); + + const validationError = dateBox.option('validationError'); + assert.ok(validationError, 'Validation error should be specified'); + assert.ok(validationError.editorSpecific, 'editorSpecific flag should be added'); + }); + + QUnit.test('correct value in input should mark datebox as valid but keep text', function(assert) { + const $dateBox = $('#dateBox').dxDateBox({ + value: null, + type: 'date', + pickerType: 'calendar' + }); + + const dateBox = $dateBox.dxDateBox('instance'); + const $input = $dateBox.find('.' + TEXTEDITOR_INPUT_CLASS); + const keyboard = keyboardMock($input); + + keyboard + .type('blabla') + .change(); + + $input.val(''); + keyboard + .type('3/2/2014') + .change(); + + assert.equal($input.val(), '3/2/2014', 'input value should not be erased'); + assert.deepEqual(dateBox.option('value'), new Date(2014, 2, 2), 'Editor\'s value should be set'); + assert.strictEqual(dateBox.option('isValid'), true, 'Editor should be marked as valid'); + assert.strictEqual(dateBox.option('validationError'), null, 'No validation error should be specified for valid input'); + }); + + QUnit.test('calendar picker should be used on generic device by default and \'type\' is \'date\'', function(assert) { + const currentDevice = devices.current(); + const realDevice = devices.real(); + + devices.real({ platform: 'generic', deviceType: 'desktop', phone: false }); + devices.current({ deviceType: 'desktop' }); + + try { + const $dateBox = $('#dateBox').dxDateBox(); + const instance = $dateBox.dxDateBox('instance'); + + assert.equal(instance.option('pickerType'), 'calendar'); + assert.equal(instance._strategy.NAME, 'Calendar'); + } finally { + devices.current(currentDevice); + devices.real(realDevice); + } + }); + + QUnit.test('calendar picker should not be used on generic device by default and \'type\' is not \'date\'', function(assert) { + const currentDevice = devices.current(); + devices.current({ platform: 'generic', deviceType: 'desktop' }); + + try { + const $dateBox = $('#dateBox').dxDateBox({ + pickerType: 'calendar', + type: 'time' + }); + assert.ok(!$dateBox.hasClass(DATEBOX_CLASS + '-calendar')); + } finally { + devices.current(currentDevice); + } + }); + + QUnit.test('calendar picker should not be used on mobile device by default', function(assert) { + const realDevice = devices.real(); + devices.real({ platform: 'android' }); + + try { + const $dateBox = $('#dateBox').dxDateBox(); + assert.ok(!$dateBox.hasClass(DATEBOX_CLASS + '-calendar')); + } finally { + devices.real(realDevice); + } + }); + + QUnit.test('correct default value for \'minZoomLevel\' option', function(assert) { + const instance = $('#dateBox').dxDateBox({ + type: 'date', + pickerType: 'calendar', + opened: true + }).dxDateBox('instance'); + + const calendar = getInstanceWidget(instance); + + assert.equal(calendar.option('minZoomLevel'), 'century', '\'minZoomLevel\' option value is correct'); + }); + + QUnit.test('correct default value for \'maxZoomLevel\' option', function(assert) { + const instance = $('#dateBox').dxDateBox({ + type: 'date', + pickerType: 'calendar', + opened: true + }).dxDateBox('instance'); + + const calendar = getInstanceWidget(instance); + + assert.equal(calendar.option('maxZoomLevel'), 'month', '\'maxZoomLevel\' option value is correct'); + }); + + QUnit.test('DateBox \'minZoomLevel\' option should affect on Calendar \'minZoomLevel\' option', function(assert) { + const instance = $('#dateBox').dxDateBox({ + type: 'date', + pickerType: 'calendar', + calendarOptions: { minZoomLevel: 'year' }, + opened: true + }).dxDateBox('instance'); + + let calendar = getInstanceWidget(instance); + + assert.equal(calendar.option('minZoomLevel'), 'year', 'calendar \'minZoomLevel\' option is correct on init'); + + instance.close(); + instance.option('calendarOptions.minZoomLevel', 'month'); + instance.open(); + calendar = getInstanceWidget(instance); + + assert.equal(calendar.option('minZoomLevel'), 'month', 'calendar \'minZoomLevel\' option after dateBox option change'); + }); + + QUnit.test('DateBox \'maxZoomLevel\' option should affect on Calendar \'maxZoomLevel\' option', function(assert) { + const instance = $('#dateBox').dxDateBox({ + type: 'date', + pickerType: 'calendar', + calendarOptions: { maxZoomLevel: 'century' }, + opened: true + }).dxDateBox('instance'); + + let calendar = getInstanceWidget(instance); + + assert.equal(calendar.option('maxZoomLevel'), 'century', 'calendar \'maxZoomLevel\' option is correct on init'); + + instance.close(); + instance.option('calendarOptions.maxZoomLevel', 'year'); + instance.open(); + calendar = getInstanceWidget(instance); + + assert.equal(calendar.option('maxZoomLevel'), 'year', 'calendar \'maxZoomLevel\' option after dateBox option change'); + }); + + QUnit.test('T208534 - calendar value should depend on datebox text option', function(assert) { + const instance = $('#dateBox').dxDateBox({ + type: 'date', + pickerType: 'calendar', + value: new Date(2015, 4, 12), + valueChangeEvent: 'keyup' + }).dxDateBox('instance'); + + const kb = keyboardMock(instance._input()); + + kb + .press('end') + .press('backspace') + .type('4'); + + instance.open(); + assert.deepEqual(new Date(2014, 4, 12), instance._strategy._widget.option('value'), 'calendar value is correct'); + }); + + QUnit.test('calendar value should depend on datebox text option when calendar is opened', function(assert) { + const instance = $('#dateBox').dxDateBox({ + type: 'date', + pickerType: 'calendar', + value: new Date(2015, 4, 12), + valueChangeEvent: 'keyup', + opened: true + }).dxDateBox('instance'); + + const kb = keyboardMock(instance._input()); + const calendar = instance._strategy._widget; + + kb + .caret(9) + .press('backspace') + .type('4'); + + assert.deepEqual(new Date(2014, 4, 12), calendar.option('value'), 'calendar value is correct'); + + kb.press('backspace'); + assert.deepEqual(new Date(201, 4, 12), calendar.option('value'), 'calendar value is correct'); + + kb.type('3'); + assert.deepEqual(new Date(2013, 4, 12), calendar.option('value'), 'calendar value is correct'); + }); + + QUnit.test('changing \'displayFormat\' should update input value', function(assert) { + const $dateBox = $('#dateBox').dxDateBox({ + value: new Date('03/10/2015'), + pickerType: 'calendar', + type: 'date' + }); + const dateBox = $dateBox.dxDateBox('instance'); + dateBox.option('displayFormat', 'shortDateShortTime'); + + assert.equal($dateBox.find('.' + TEXTEDITOR_INPUT_CLASS).val(), '3/10/2015, 12:00 AM', 'input value is updated'); + }); + + QUnit.test('displayFormat should affect on timeView', function(assert) { + const $dateBox = $('#dateBox').dxDateBox({ + value: new Date('03/10/2015'), + displayFormat: 'shortdateshorttime', + pickerType: 'calendar', + opened: true, + type: 'datetime' + }); + + const dateBox = $dateBox.dxDateBox('instance'); + const $content = $(dateBox._popup.$content()); + const timeView = $content.find('.' + TIMEVIEW_CLASS).dxTimeView('instance'); + + assert.notOk(timeView.option('use24HourFormat'), 'using 12 hour format'); + + dateBox.option('displayFormat', 'hour'); + assert.ok(timeView.option('use24HourFormat'), 'using 24 hour format'); + }); + + QUnit.test('disabledDates correctly displays', function(assert) { + const instance = $('#dateBox').dxDateBox({ + type: 'date', + pickerType: 'calendar', + value: new Date(2015, 4, 12), + disabledDates: [new Date(2015, 4, 13)], + opened: true + }).dxDateBox('instance'); + + const calendar = getInstanceWidget(instance); + const $disabledCell = calendar.$element().find('.dx-calendar-empty-cell'); + + assert.equal($disabledCell.length, 1, 'There is one disabled cell'); + assert.equal($disabledCell.text(), '13', 'Correct cell is disabled'); + }); + + QUnit.test('disabledDates should not be called for the dates out of range[min, max]', function(assert) { + let callCount = 0; + + $('#dateBox').dxDateBox({ + type: 'date', + pickerType: 'calendar', + value: new Date(2019, 11, 10), + min: new Date(2019, 11, 15), + max: new Date(2019, 11, 20), + disabledDates: () => { + ++callCount; + return true; + }, + opened: true + }).dxDateBox('instance'); + + assert.equal(callCount, 12, 'disabledDates has been called 6 times on init, 6 times on [min; max] for focusing'); + }); + + QUnit.test('disabledDates correctly displays after optionChanged', function(assert) { + const instance = $('#dateBox').dxDateBox({ + type: 'date', + pickerType: 'calendar', + value: new Date(2015, 4, 12), + disabledDates: [new Date(2015, 4, 13)], + opened: true + }).dxDateBox('instance'); + + instance.option('disabledDates', e => { + if(e.date.getDate() === 14 && e.date.getMonth() === 3) { + return true; + } + }); + + const calendar = getInstanceWidget(instance); + const $disabledCell = calendar.$element().find('.dx-calendar-empty-cell'); + + assert.equal($disabledCell.length, 1, 'There is one disabled cell'); + assert.equal($disabledCell.text(), '14', 'Correct cell is disabled'); + }); + + QUnit.test('disabledDates argument contains correct component parameter', function(assert) { + const stub = sinon.stub(); + + $('#dateBox').dxDateBox({ + type: 'date', + pickerType: 'calendar', + value: new Date(2015, 4, 12), + disabledDates: stub, + opened: true + }); + + const component = stub.lastCall.args[0].component; + assert.equal(component.NAME, 'dxDateBox', 'Correct component'); + }); + + QUnit.test('datebox with the \'datetime\' type should keep event subscriptions', function(assert) { + const stub = sinon.stub(); + + const dateBox = $('#dateBox').dxDateBox({ + type: 'datetime', + pickerType: 'calendar', + value: new Date(2015, 4, 12), + adaptivityEnabled: true, + onInitialized(e) { + e.component.on('optionChanged', stub); + } + }).dxDateBox('instance'); + + assert.equal(stub.callCount, 1, 'set text on render'); + + dateBox.option('opened', true); + + assert.equal(stub.callCount, 2, '\'opened\' optionChanged event has been raised'); + }); + + QUnit.test('Today button should be hidden if calendar is hidden', function(assert) { + const $element = $('#dateBox').dxDateBox({ + pickerType: 'calendar', + type: 'datetime', + calendarOptions: { + visible: false + }, + opened: true + }); + const instance = $element.dxDateBox('instance'); + const $todayButton = $(instance.content()).parent().find('.dx-button-today'); + + assert.strictEqual($todayButton.length, 0); + }); + + + QUnit.test('Today button should be hidden if calendar visibility is changed', function(assert) { + const $element = $('#dateBox').dxDateBox({ + pickerType: 'calendar', + type: 'datetime', + opened: true + }); + const instance = $element.dxDateBox('instance'); + + instance.option('calendarOptions.visible', false); + assert.strictEqual($(instance.content()).parent().find('.dx-button-today').length, 0); + + instance.option('calendarOptions.visible', true); + assert.strictEqual($(instance.content()).parent().find('.dx-button-today').length, 1); + }); + + QUnit.test('change year via scroll should log proper year in on value change event (T1229926)', function(assert) { + const valueChangedHandle = sinon.spy(); + const date = new Date(); + const currentYear = date.getFullYear(); + const datebox = $('#dateBox').dxDateBox({ + type: 'date', + value: date, + displayFormat: 'M/dd/yyyy', + valueChangeEvent: 'dxmousewheel', + useMaskBehavior: true, + onValueChanged: valueChangedHandle + }).dxDateBox('instance'); + + const $input = $(datebox.element()).find(`.${TEXTEDITOR_INPUT_CLASS}`); + const pointer = pointerMock($input); + const keyboard = keyboardMock($input, true); + + keyboard.caret({ start: 12, end: 15 }); + + $input.trigger('dxclick'); + + pointer.wheel(1); + + let changedValue = valueChangedHandle.getCall(0).args[0]; + assert.strictEqual(valueChangedHandle.callCount, 1, 'handler has been called once'); + assert.deepEqual(new Date(changedValue.value).getFullYear(), currentYear + 1, 'value year is correct'); assert.deepEqual(new Date(changedValue.previousValue).getFullYear(), currentYear, 'previous value year is correct'); + + pointer.wheel(1); + + changedValue = valueChangedHandle.getCall(1).args[0]; + assert.strictEqual(valueChangedHandle.callCount, 2, 'handler has been called twice'); + assert.deepEqual(new Date(changedValue.value).getFullYear(), currentYear + 2, 'value year is correct'); + assert.deepEqual(new Date(changedValue.previousValue).getFullYear(), currentYear + 1, 'previous value year is correct'); + }); +}); + +QUnit.module('datebox w/ calendar', { + beforeEach: function() { + this.clock = sinon.useFakeTimers(new Date().valueOf()); + fx.off = true; + + this.fixture = new DevExpress.ui.testing.DateBoxFixture('#dateBox', { + value: currentDate, + calendarOptions: { + currentDate, + firstDayOfWeek + }, + pickerType: 'calendar' + }); + this.reinitFixture = (options) => { + this.fixture.dispose(); + this.fixture = new DevExpress.ui.testing.DateBoxFixture('#dateBox', options); + }; + }, + afterEach: function() { + this.fixture.dispose(); + fx.off = false; + this.clock.restore(); + } +}, () => { + QUnit.test('DateBox is defined', function(assert) { + assert.ok(this.fixture.dateBox); + }); + + QUnit.test('DateBox can be instantiated', function(assert) { + assert.ok(this.fixture.dateBox instanceof DateBox); + }); + + QUnit.test('DateBox must render an input', function(assert) { + assert.ok(this.fixture.input.length); + }); + + QUnit.test('open must set \'opened\' option', function(assert) { + assert.ok(!this.fixture.dateBox.option('opened')); + this.fixture.dateBox.open(); + assert.ok(this.fixture.dateBox.option('opened')); + }); + + QUnit.test('calendarOptions must be passed to dxCalendar on initialization', function(assert) { + this.fixture.dateBox.open(); + currentDate.setDate(1); + assert.deepEqual(getInstanceWidget(this.fixture.dateBox).option('currentDate'), currentDate); + assert.deepEqual(getInstanceWidget(this.fixture.dateBox).option('firstDayOfWeek'), firstDayOfWeek); + }); + + QUnit.test('Clicking _calendarContainer must not close dropDown', function(assert) { + this.fixture.dateBox.open(); + pointerMock(this.fixture.dateBox._calendarContainer).click(); + assert.ok(this.fixture.dateBox.option('opened')); + }); + + QUnit.test('DateBox must update the input value when the value option changes', function(assert) { + const date = new Date(2011, 11, 11); + this.fixture.dateBox.option('value', date); + assert.deepEqual(this.fixture.input.val(), dateLocalization.format(date, this.fixture.format)); + }); + + QUnit.test('DateBox must immediately display \'value\' passed via the constructor on rendering', function(assert) { + const date = new Date(2010, 10, 10); + + this.reinitFixture({ + value: date, + calendarOptions: { currentDate, firstDayOfWeek }, + pickerType: 'calendar' + }); + + assert.deepEqual(this.fixture.input.val(), dateLocalization.format(date, this.fixture.format)); + }); + + QUnit.test('DateBox should pass empty string value to calendar if value is empty string', function(assert) { + this.reinitFixture({ + value: '', + pickerType: 'calendar', + opened: true + }); + + assert.equal(this.fixture.dateBox._strategy._widget.option('value'), '', 'value is equal to empty string'); + }); + + QUnit.test('DateBox must show the calendar with a proper date selected', function(assert) { + const date = new Date(2011, 11, 11); + this.fixture.dateBox.option('value', date); + this.fixture.dateBox.open(); + assert.deepEqual(getInstanceWidget(this.fixture.dateBox).option('value'), date); + }); + + QUnit.test('DateBox must update its value when a date is selected in the calendar when applyValueMode=\'instantly\'', function(assert) { + const date = new Date(2011, 11, 11); + + this.reinitFixture({ + applyValueMode: 'instantly', + pickerType: 'calendar' + }); + + this.fixture.dateBox.open(); + getInstanceWidget(this.fixture.dateBox).option('value', date); + // this.fixture.dateBox.close(); + assert.strictEqual(this.fixture.dateBox.option('value'), date); + }); + + QUnit.test('DateBox must update the calendar value when the CalendarPicker.option(\'value\') changes', function(assert) { + this.reinitFixture({ + applyValueMode: 'useButtons', + pickerType: 'calendar', + }); + + const date = new Date(2011, 11, 11); + this.fixture.dateBox.open(); + this.fixture.dateBox.option('value', date); + assert.deepEqual(getInstanceWidget(this.fixture.dateBox).option('value'), date); + }); + + QUnit.test('When typing a correct date, dateBox must not make a redundant _setInputValue call', function(assert) { + let _setInputValueCallCount = 0; + + const mockSetInputValue = () => { + ++_setInputValueCallCount; + }; + + this.fixture.dateBox._setInputValue = mockSetInputValue; + this.fixture.dateBox.open(); + this.fixture.typeIntoInput('11/11/2011', this.fixture.input); + assert.strictEqual(_setInputValueCallCount, 0); + }); + + QUnit.test('Swiping must not close the calendar', function(assert) { + $(this.fixture.dateBox._input()).focus(); + this.fixture.dateBox.open(); + pointerMock(this.fixture.dateBox._strategy._calendarContainer).start().swipeStart().swipeEnd(1); + assert.strictEqual(this.fixture.dateBox._input()[0], getActiveElement()); + }); + + QUnit.test('Pressing escape must hide the calendar and clean focus', function(assert) { + const escapeKeyDown = $.Event('keydown', { key: 'Escape' }); + this.fixture.dateBox.option('focusStateEnabled', true); + this.fixture.dateBox.open(); + $(this.fixture.dateBox._input()).trigger(escapeKeyDown); + assert.ok(!this.fixture.dateBox.option('opened')); + assert.ok(!this.fixture.dateBox._input().is(':focus')); + }); + + QUnit.test('dateBox must show the calendar with proper LTR-RTL mode', function(assert) { + this.fixture.dateBox.option('rtlEnabled', true); + this.fixture.dateBox.open(); + assert.ok(getInstanceWidget(this.fixture.dateBox).option('rtlEnabled')); + }); + + QUnit.test('dateBox should not reposition the calendar icon in RTL mode', function(assert) { + let iconRepositionCount = 0; + + const _repositionCalendarIconMock = () => { + ++iconRepositionCount; + }; + + this.fixture.dateBox._repositionCalendarIcon = _repositionCalendarIconMock; + this.fixture.dateBox.option('rtl', true); + assert.strictEqual(iconRepositionCount, 0); + }); + + QUnit.test('dateBox must apply the wrapper class with appropriate picker type to the drop-down overlay wrapper', function(assert) { + const dateBox = this.fixture.dateBox; + dateBox.open(); + assert.ok(this.fixture.dateBox._popup.$wrapper().hasClass(DATEBOX_WRAPPER_CLASS + '-' + dateBox.option('pickerType'))); + }); + + QUnit.test('dateBox must correctly reopen the calendar after refreshing when it was not hidden beforehand', function(assert) { + this.fixture.dateBox.open(); + this.fixture.dateBox._refresh(); + assert.ok(this.fixture.dateBox._$popup.dxPopup('instance').option('visible')); + }); + + QUnit.test('Changing the \'value\' option must invoke the \'onValueChanged\' action', function(assert) { + this.fixture.dateBox.option('onValueChanged', () => { + assert.ok(true); + }); + this.fixture.dateBox.option('value', new Date(2015, 6, 14)); + }); + + QUnit.test('dateBox\'s \'min\' and \'max\' options equal to undefined (T171537)', function(assert) { + assert.strictEqual(this.fixture.dateBox.option('min'), undefined); + assert.strictEqual(this.fixture.dateBox.option('max'), undefined); + }); + + QUnit.test('dateBox must pass min and max to the created calendar', function(assert) { + const min = new Date(2010, 9, 10); + const max = new Date(2010, 11, 10); + this.reinitFixture({ + min, + max, + pickerType: 'calendar' + }); + this.fixture.dateBox.open(); + assert.ok(dateUtils.dateInRange(getInstanceWidget(this.fixture.dateBox).option('currentDate'), min, max)); + }); + + QUnit.test('dateBox should not change value when setting to an earlier date than min; and setting to a later date than max', function(assert) { + const min = new Date(2010, 10, 5); + const max = new Date(2010, 10, 25); + const earlyDate = new Date(min.getFullYear(), min.getMonth(), min.getDate() - 1); + const lateDate = new Date(max.getFullYear(), max.getMonth(), max.getDate() + 1); + + this.reinitFixture({ + min, + max, + pickerType: 'calendar' + }); + + this.fixture.dateBox.option('value', earlyDate); + assert.deepEqual(this.fixture.dateBox.option('value'), earlyDate); + + this.fixture.dateBox.option('value', lateDate); + assert.deepEqual(this.fixture.dateBox.option('value'), lateDate); + }); + + QUnit.test('should execute custom validator while validation state reevaluating', function(assert) { + this.reinitFixture({ opened: true }); + + const dateBox = this.fixture.dateBox; + + dateBox.$element().dxValidator({ + validationRules: [{ + type: 'custom', + validationCallback: () => false + }] + }); + + const cell = dateBox._popup.$wrapper().find(`.${CALENDAR_CELL_CLASS}`); + + assert.ok(dateBox.option('isValid')); + assert.strictEqual(dateBox.option('text'), ''); + + $(cell).trigger('dxclick'); + + assert.notOk(dateBox.option('isValid')); + assert.notStrictEqual(dateBox.option('text'), ''); + }); + + QUnit.test('should rise validation event once after value is changed by calendar (T714599)', function(assert) { + const validationCallbackStub = sinon.stub().returns(false); + const dateBox = $('#dateBoxWithPicker') + .dxDateBox({ + type: 'datetime', + pickerType: 'calendar', + value: new Date(2015, 5, 9, 15, 54, 13), + opened: true + }) + .dxValidator({ + validationRules: [{ + type: 'custom', + validationCallback: validationCallbackStub + }] + }) + .dxDateBox('instance'); + + $(`.${CALENDAR_CELL_CLASS}`).eq(0).trigger('dxclick'); + $(APPLY_BUTTON_SELECTOR).trigger('dxclick'); + + assert.notOk(dateBox.option('opened')); + assert.ok(validationCallbackStub.calledOnce); + }); + + QUnit.test('Editor should reevaluate validation state after change text to the current value', function(assert) { + this.reinitFixture({ + min: new Date(2010, 10, 5), + value: new Date(2010, 10, 10), + type: 'date', + pickerType: 'calendar' + }); + + const dateBox = this.fixture.dateBox; + + $(dateBox._input()) + .val('11/3/2010') + .change(); + + assert.notOk(dateBox.option('isValid'), 'Editor isn\'t valid'); + assert.equal(dateBox.option('text'), '11/3/2010'); + + dateBox.open(); + + const $selectedDate = dateBox._popup.$wrapper().find('.dx-calendar-selected-date'); + $($selectedDate).trigger('dxclick'); + + assert.ok(dateBox.option('isValid'), 'Editor is valid'); + assert.equal(dateBox.option('text'), '11/10/2010'); + }); + + QUnit.test('In dateTime strategy buttons should be placed in popup bottom', function(assert) { + this.reinitFixture({ + type: 'datetime', + applyValueMode: 'useButtons', + pickerType: 'calendar' + }); + + this.fixture.dateBox.open(); + + assert.equal($('.dx-popup-bottom .dx-button').length, 3, 'two buttons is in popup bottom'); + }); + + QUnit.test('Click on apply button', function(assert) { + const onValueChangedHandler = sinon.spy(noop); + const newDate = new Date(2010, 10, 10); + + this.reinitFixture({ + onValueChanged: onValueChangedHandler, + applyValueMode: 'useButtons', + pickerType: 'calendar' + }); + this.fixture.dateBox.open(); + getInstanceWidget(this.fixture.dateBox).option('value', newDate); + $(APPLY_BUTTON_SELECTOR).eq(0).trigger('dxclick'); + assert.equal(this.fixture.dateBox.option('opened'), false); + assert.deepEqual(this.fixture.dateBox.option('value'), newDate); + assert.ok(onValueChangedHandler.calledOnce); + }); + + QUnit.test('Click on cancel button', function(assert) { + const onValueChangedHandler = sinon.spy(noop); + const oldDate = new Date(2008, 8, 8); + const newDate = new Date(2010, 10, 10); + + this.reinitFixture({ + value: oldDate, + onValueChanged: onValueChangedHandler, + applyValueMode: 'useButtons', + pickerType: 'calendar' + }); + + this.fixture.dateBox.open(); + getInstanceWidget(this.fixture.dateBox).option('value', newDate); + $('.dx-popup-cancel.dx-button').eq(0).trigger('dxclick'); + + assert.equal(this.fixture.dateBox.option('opened'), false); + assert.equal(this.fixture.dateBox.option('value'), oldDate); + assert.ok(!onValueChangedHandler.calledOnce); + }); + + QUnit.test('calendar does not open on field click (T189394)', function(assert) { + assert.ok(!this.fixture.dateBox.option('openOnFieldClick')); + }); + + const getLongestCaptionIndex = uiDateUtils.getLongestCaptionIndex; + const getLongestDate = uiDateUtils.getLongestDate; + + QUnit.test('getLongestDate must consider the possibility of overflowing to the next month from its 28th day and thus losing the longest month name when calculating widths for formats containing day and month names', function(assert) { + const someLanguageMonthNames = ['1', '1', '1', '1', '1', '1', '1', '1', '1', '22', '1', '1']; + const someLanguageDayNames = ['1', '1', '1', '1', '22', '1', '1']; + const longestMonthNameIndex = getLongestCaptionIndex(someLanguageMonthNames); + const longestDate = getLongestDate('D', someLanguageMonthNames, someLanguageDayNames); + assert.strictEqual(longestDate.getMonth(), longestMonthNameIndex); + }); + + QUnit.test('Calendar should update it value accordingly \'text\' option if it is valid (T189474)', function(assert) { + const date = new Date(2014, 5, 10); + + this.reinitFixture({ + value: date, + pickerType: 'calendar' + }); + + this.fixture.dateBox.open(); + + keyboardMock(this.fixture.input) + .caret(9) + .press('backspace') + .type('5'); + + this.fixture.input.trigger('change'); + this.fixture.dateBox.open(); + + const calendar = getInstanceWidget(this.fixture.dateBox); + assert.deepEqual(calendar.option('value'), new Date(2015, 5, 10)); + }); + + QUnit.test('Calendar should not be closed after datebox value has been changed by input', function(assert) { + const date = new Date(2014, 5, 10); + + this.reinitFixture({ + value: date, + applyValueMode: 'useButtons', + pickerType: 'calendar' + }); + + this.fixture.dateBox.open(); + + keyboardMock(this.fixture.input) + .caret(9) + .press('backspace') + .type('5'); + + this.fixture.input.trigger('change'); + + const calendar = getInstanceWidget(this.fixture.dateBox); + assert.deepEqual(calendar.option('value'), new Date(2015, 5, 10)); + assert.ok(this.fixture.dateBox.option('opened')); + }); + + QUnit.test('Value should be changed only after click on \'Apply\' button if the \'applyValueMode\' options is changed to \'useButtons\'', function(assert) { + const value = new Date(2015, 0, 20); + const newValue = new Date(2015, 0, 30); + + const dateBox = this.fixture.dateBox; + + dateBox.option('value', value); + dateBox.open(); + dateBox.close(); + dateBox.option('applyValueMode', 'useButtons'); + + dateBox.open(); + const calendar = getInstanceWidget(dateBox); + const $applyButton = dateBox._popup.$wrapper().find(APPLY_BUTTON_SELECTOR).eq(0); + + calendar.option('value', newValue); + assert.deepEqual(dateBox.option('value'), value, 'value is not changed yet'); + + $($applyButton).trigger('dxclick'); + assert.deepEqual(dateBox.option('value'), newValue, 'value is changed after click'); + }); + + QUnit.test('Value should be changed if it was entered from keyboard and it is out of range', function(assert) { + const value = new Date(2015, 0, 15); + const min = new Date(2015, 0, 10); + const max = new Date(2015, 0, 20); + + this.reinitFixture({ + value, + min, + max, + pickerType: 'calendar' + }); + + const dateBox = this.fixture.dateBox; + const $input = $(dateBox.$element().find(`.${TEXTEDITOR_INPUT_CLASS}`)); + const kb = keyboardMock($input); + const inputValue = '1/5/2015'; + + clearInput($input, kb); + kb.type(inputValue).change(); + assert.equal($input.val(), inputValue, 'input value is correct'); + assert.deepEqual(dateBox.option('value'), value, 'value has not been changed'); + assert.ok(!dateBox.option('isValid'), 'datebox value is invalid'); + + const validationError = dateBox.option('validationError'); + assert.ok(validationError, 'Validation error should be specified'); + assert.ok(validationError.editorSpecific, 'editorSpecific flag should be added'); + }); + + QUnit.test('Empty value should not be marked as \'out of range\'', function(assert) { + const value = new Date(2015, 0, 15); + const min = new Date(2015, 0, 10); + const max = new Date(2015, 0, 20); + + this.reinitFixture({ + value, + min, + max, + pickerType: 'calendar' + }); + + const dateBox = this.fixture.dateBox; + const $input = $(dateBox.$element().find(`.${TEXTEDITOR_INPUT_CLASS}`)); + const kb = keyboardMock($input); + + clearInput($input, kb); + kb.change(); + assert.ok(dateBox.option('isValid'), 'isValid flag should be set'); + assert.ok(!dateBox.option('validationError'), 'validationError should not be set'); + }); + + QUnit.test('Popup should not be hidden after value change using keyboard', function(assert) { + const value = new Date(2015, 0, 29); + + this.reinitFixture({ + type: 'date', + value, + pickerType: 'calendar' + }); + + const dateBox = this.fixture.dateBox; + const $input = $(dateBox.$element().find(`.${TEXTEDITOR_INPUT_CLASS}`)); + const kb = keyboardMock($input); + + dateBox.open(); + assert.equal($input.val(), '1/29/2015', 'correct input value'); + + kb + .caret(9) + .press('backspace') + .type('6') + .change(); + + assert.equal($input.val(), '1/29/2016', 'input value is changed'); + assert.ok(dateBox.option('opened'), 'popup is still opened'); + }); + + QUnit.test('T196443 - dxDateBox should not hide popup after erase date in input field', function(assert) { + const value = new Date(2015, 0, 30); + + this.reinitFixture({ + value, + min: null, + max: null, + type: 'date', + pickerType: 'calendar' + }); + + const dateBox = this.fixture.dateBox; + const $input = dateBox._input(); + const kb = keyboardMock($input); + + dateBox.open(); + kb.press('end'); + + for(let i = 0; i < 10; i++) { + kb.press('backspace'); + } + + assert.deepEqual(dateBox.option('value'), value, 'datebox value is not changed'); + assert.ok(dateBox.option('opened'), 'popup is still opened'); + }); + + QUnit.test('T203457 - popup should be closed when selected date is clicked', function(assert) { + const value = new Date(2015, 1, 1); + + this.reinitFixture({ + value, + min: null, + max: null, + type: 'date', + pickerType: 'calendar' + }); + + const dateBox = this.fixture.dateBox; + dateBox.open(); + const $selectedDate = dateBox._popup.$wrapper().find('.dx-calendar-selected-date'); + $($selectedDate).trigger('dxclick'); + + assert.ok(!dateBox.option('opened'), 'popup is closed'); + }); + + QUnit.test('T208825 - tapping on the \'enter\' should change value if popup is opened', function(assert) { + const value = new Date(2015, 2, 13); + + this.reinitFixture({ + value, + focusStateEnabled: true, + pickerType: 'calendar' + }); + + const dateBox = this.fixture.dateBox; + const $input = dateBox._input(); + const kb = keyboardMock($input); + + dateBox.option('valueChangeEvent', 'keyup'); + dateBox.open(); + + kb + .caret(9) + .press('backspace') + .type('4') + .press('enter'); + + assert.deepEqual(dateBox.option('value'), new Date(2014, 2, 13), 'value is changed'); + }); + + QUnit.test('Close popup on the \'enter\' press after input value is changed', function(assert) { + const value = new Date(2015, 2, 10); + + this.reinitFixture({ + value, + focusStateEnabled: true, + pickerType: 'calendar' + }); + + const dateBox = this.fixture.dateBox; + + dateBox.open(); + + keyboardMock(dateBox._input()) + .press('end') + .press('backspace') + .type('4') + .press('enter'); + + assert.equal(dateBox.option('opened'), false, 'popup is still opened'); + }); + + QUnit.test('repaint was fired if strategy is fallback', function(assert) { + this.reinitFixture({ + useNative: false, + useCalendar: true, + type: 'datetime', + pickerType: 'calendarWithTime', + opened: true + }); + + const dateBox = this.fixture.dateBox; + const popup = dateBox.$element().find('.dx-popup').dxPopup('instance'); + const repaintSpy = sinon.spy(popup, 'repaint'); + + this.clock.tick(10); + + assert.ok(repaintSpy.called, 'repaint was fired on opened'); + }); + + QUnit.test('changing type from \'datetime\' to \'date\' should lead to strategy changing', function(assert) { + this.reinitFixture({ + type: 'datetime', + pickerType: 'calendar' + }); + + const dateBox = this.fixture.dateBox; + assert.equal(dateBox._strategy.NAME, 'CalendarWithTime', 'correct strategy for the \'datetime\' type'); + + dateBox.option('type', 'date'); + assert.equal(dateBox._strategy.NAME, 'Calendar', 'correct strategy for the \'date\' type'); + }); + + QUnit.test('T247493 - value is cleared when text is changed to invalid date and popup is opened', function(assert) { + const date = new Date(2015, 5, 9); + + this.reinitFixture({ + pickerType: 'calendar', + value: date + }); + + const dateBox = this.fixture.dateBox; + const $element = $(dateBox.$element()); + const $input = $element.find(`.${TEXTEDITOR_INPUT_CLASS}`); + const kb = keyboardMock($input); + + kb + .press('end') + .press('backspace'); + + dateBox.open(); + assert.deepEqual(dateBox.option('value'), date, 'value is correct'); + assert.equal($input.val(), '6/9/201', 'input value is correct'); + }); + + QUnit.test('T252170 - date time should be the same with set value after calendar value is changed', function(assert) { + const date = new Date(2015, 5, 9, 15, 54, 13); + + this.reinitFixture({ + pickerType: 'calendar', + type: 'date', + value: date + }); + + const dateBox = this.fixture.dateBox; + dateBox.open(); + const calendar = dateBox._strategy._widget; + const $calendar = $(calendar.$element()); + + $($calendar.find('.dx-calendar-cell[data-value=\'2015/06/10\']')).trigger('dxclick'); + assert.deepEqual(calendar.option('value'), new Date(2015, 5, 10, 15, 54, 13), 'new calendar value saves set value time'); + assert.deepEqual(dateBox.option('value'), new Date(2015, 5, 10, 15, 54, 13), 'new datebox value saves set value time'); + }); + + QUnit.test('calendar views should be positioned correctly', function(assert) { + $('#dateBox').dxDateBox({ + type: 'date', + pickerType: 'calendar', + value: new Date(2015, 4, 12), + opened: true + }); + + const $calendarViews = $('.dx-popup-wrapper .dx-calendar-views-wrapper .dx-widget'); + const viewWidth = $calendarViews.eq(0).width(); + + assert.equal($calendarViews.eq(0).position().left, 0, 'main view is at 0'); + assert.equal($calendarViews.eq(1).position().left, -viewWidth, 'before view is at the left'); + assert.equal($calendarViews.eq(2).position().left, viewWidth, 'after view is at the right'); + }); + + QUnit.test('Popup with calendar strategy should be use \'flipfit flip\' strategy', function(assert) { + const $dateBox = $('#dateBox').dxDateBox({ + type: 'date', + pickerType: 'calendar', + value: new Date(), + deferRendering: true + }); + + $dateBox.dxDateBox('option', 'popupPosition', { my: 'bottom left' }); + + $dateBox.dxDateBox('option', 'opened', true); + + const popup = $dateBox.find('.dx-popup').dxPopup('instance'); + + assert.equal(popup.option('position').collision, 'flipfit flip', 'collision set correctly'); + assert.equal(popup.option('position').my, 'bottom left', 'position is saved'); + }); + + QUnit.test('Popup with calendarWithTime strategy should be use \'flipfit flip\' strategy', function(assert) { + const $dateBox = $('#dateBox').dxDateBox({ + type: 'datetime', + pickerType: 'calendar', + value: new Date(), + opened: true + }); + + assert.equal($dateBox.find('.dx-popup').dxPopup('option', 'position').collision, 'flipfit flip', 'collision set correctly'); + }); + + QUnit.test('DateBox should not take current date value at the opening if value is null', function(assert) { + const $dateBox = $('#dateBox').dxDateBox({ + value: null, + pickerType: 'calendar' + }); + + const instance = $dateBox.dxDateBox('instance'); + const $dropDownButton = $dateBox.find(`.${DROP_DOWN_BUTTON_CLASS}`); + + $($dropDownButton).trigger('dxclick'); + + assert.equal(instance.option('value'), null, 'value shouldn\'t be dropped after opening'); + }); + + QUnit.test('time component should not be changed if editing value with the help of keyboard (T398429)', function(assert) { + this.reinitFixture({ + type: 'date', + pickerType: 'calendar', + value: new Date(2016, 6, 1, 14, 15), + focusStateEnabled: true + }); + + keyboardMock(this.fixture.rootElement.find('.' + TEXTEDITOR_INPUT_CLASS)) + .focus() + .caret(2) + .press('del') + .type('2') + .change(); + + const value = this.fixture.dateBox.option('value'); + assert.equal(value.getHours(), 14, 'the \'hours\' component has not been changed'); + assert.equal(value.getMinutes(), 15, 'the \'minutes\' component has not been changed'); + }); + + QUnit.test('Calendar should have single selectionMode even if another selectionMode is passed to calendarOptions', function(assert) { + this.reinitFixture({ + pickerType: 'calendar', + opened: true, + calendarOptions: { + selectionMode: 'range', + } + }); + + assert.strictEqual(this.fixture.dateBox.option('calendarOptions.selectionMode'), 'single'); + }); +}); + +QUnit.module('datebox with time component', { + beforeEach: function() { + fx.off = true; + }, + afterEach: function() { + fx.off = false; + } +}, () => { + QUnit.test('date box should contain calendar and time view inside box in large screen', function(assert) { + const originalWidthFunction = implementationsMap.getWidth; + + try { + sinon.stub(implementationsMap, 'getWidth').returns(600); + + const $element = $('#dateBox').dxDateBox({ + type: 'datetime', + pickerType: 'calendar', + adaptivityEnabled: true, + opened: true + }); + + const instance = $element.dxDateBox('instance'); + const $content = $(instance._popup.$content()); + const box = Box.getInstance($content.find('.' + BOX_CLASS)); + const $clock = $content.find('.dx-timeview-clock'); + + assert.equal(box.option('direction'), 'row', 'correct box direction specified'); + assert.ok(box.itemElements().eq(0).find('.' + CALENDAR_CLASS).length, 'calendar rendered'); + assert.ok(box.itemElements().eq(1).find('.' + TIMEVIEW_CLASS).length, 'timeview rendered'); + assert.equal($clock.length, 1, 'clock was rendered'); + } finally { + implementationsMap.getWidth = originalWidthFunction; + } + }); + + QUnit.test('date box should contain calendar and time view inside box in small screen', function(assert) { + const originalWidthFunction = implementationsMap.getWidth; + + try { + sinon.stub(implementationsMap, 'getWidth').returns(300); + + const $element = $('#dateBox').dxDateBox({ + type: 'datetime', + pickerType: 'calendar', + adaptivityEnabled: true, + opened: true + }); + + const instance = $element.dxDateBox('instance'); + const $content = $(instance._popup.$content()); + const box = Box.getInstance($content.find('.' + BOX_CLASS)); + const $clock = $content.find('.dx-timeview-clock'); + + assert.equal(box.option('direction'), 'row', 'correct box direction specified'); + assert.ok(box.itemElements().eq(0).find('.' + CALENDAR_CLASS).length, 'calendar rendered'); + assert.ok(box.itemElements().eq(0).find('.' + TIMEVIEW_CLASS).length, 'timeview rendered'); + assert.equal($clock.length, 0, 'clock was not rendered'); + } finally { + implementationsMap.getWidth = originalWidthFunction; + } + }); + + [true, false].forEach((adaptivityEnabledValue) => { + QUnit.test(`date box should change behavior if adaptivityEnabled option is changed to ${adaptivityEnabledValue} at runtime`, function(assert) { + const widthStub = sinon.stub(implementationsMap, 'getWidth').returns(300); + + try { + const $element = $('#dateBox').dxDateBox({ + type: 'datetime', + pickerType: 'calendar', + adaptivityEnabled: !adaptivityEnabledValue, + opened: true + }); + const instance = $element.dxDateBox('instance'); + + instance.option('adaptivityEnabled', adaptivityEnabledValue); + instance.close(); + instance.open(); + + const $content = $(instance._popup.$content()); + const box = Box.getInstance($content.find(`.${BOX_CLASS}`)); + const $clock = $content.find(`.${TIMEVIEW_CLOCK_CLASS}`); + const timeViewExpectedMessage = `timeview is ${adaptivityEnabledValue ? '' : 'not'} rendered`; + const clockExpectedMessage = `clock is ${adaptivityEnabledValue ? 'not' : ''} rendered`; + + assert.strictEqual(box.itemElements().eq(0).find(`.${TIMEVIEW_CLASS}`).length, (adaptivityEnabledValue ? 1 : 0), timeViewExpectedMessage); + assert.strictEqual($clock.length, (adaptivityEnabledValue ? 0 : 1), clockExpectedMessage); + } finally { + widthStub.restore(); + } + }); + }); + + [true, false].forEach((showAnalogClockValue) => { + const timeViewExpectedMessage = `timeview is ${showAnalogClockValue ? 'not' : ''} rendered`; + const clockExpectedMessage = `clock is ${showAnalogClockValue ? '' : 'not'} rendered`; + + QUnit.test(`date box should ${showAnalogClockValue ? 'not' : ''} have compact view when showAnalogClock option is ${showAnalogClockValue}`, function(assert) { + const $element = $('#dateBox').dxDateBox({ + type: 'datetime', + pickerType: 'calendar', + showAnalogClock: showAnalogClockValue + }); + + const instance = $element.dxDateBox('instance'); + instance.open(); + + const $content = $(instance._popup.$content()); + const box = Box.getInstance($content.find(`.${BOX_CLASS}`)); + const $clock = $content.find('.dx-timeview-clock'); + + assert.strictEqual(box.option('direction'), 'row', 'correct box direction specified'); + assert.strictEqual(box.itemElements().eq(0).find(`.${CALENDAR_CLASS}`).length, 1, 'calendar rendered'); + assert.strictEqual(box.itemElements().eq(0).find(`.${TIMEVIEW_CLASS}`).length, (showAnalogClockValue ? 0 : 1), timeViewExpectedMessage); + assert.strictEqual($clock.length, (showAnalogClockValue ? 1 : 0), clockExpectedMessage); + }); + + QUnit.test(`date box should change behavior if showAnalogClock option is changed to ${showAnalogClockValue} at runtime`, function(assert) { + const $element = $('#dateBox').dxDateBox({ + type: 'datetime', + pickerType: 'calendar', + showAnalogClock: !showAnalogClockValue, + opened: true + }); + const instance = $element.dxDateBox('instance'); + + instance.option('showAnalogClock', showAnalogClockValue); + instance.close(); + instance.open(); + + const $content = $(instance._popup.$content()); + const box = Box.getInstance($content.find(`.${BOX_CLASS}`)); + const $clock = $content.find(`.${TIMEVIEW_CLOCK_CLASS}`); + assert.strictEqual(box.itemElements().eq(0).find(`.${TIMEVIEW_CLASS}`).length, (showAnalogClockValue ? 0 : 1), timeViewExpectedMessage); + assert.strictEqual($clock.length, (showAnalogClockValue ? 1 : 0), clockExpectedMessage); + }); + }); + + QUnit.test('date box wrapper adaptivity class depends on the screen size', function(assert) { + const LARGE_SCREEN_SIZE = 2000; + const SMALL_SCREEN_SIZE = 300; + + let stub = sinon.stub(implementationsMap, 'getWidth').returns(LARGE_SCREEN_SIZE); + + try { + const instance = $('#dateBox').dxDateBox({ + type: 'datetime', + pickerType: 'calendar', + adaptivityEnabled: true, + opened: true + }).dxDateBox('instance'); + + assert.notOk(instance._popup.$wrapper().hasClass(DATEBOX_ADAPTIVITY_MODE_CLASS), 'there is no adaptivity class for the large screen'); + + instance.close(); + + stub.restore(); + stub = sinon.stub(implementationsMap, 'getWidth').returns(SMALL_SCREEN_SIZE); + + instance.open(); + assert.ok(instance._popup.$wrapper().hasClass(DATEBOX_ADAPTIVITY_MODE_CLASS), 'there is the adaptivity class for the small screen'); + } finally { + stub.restore(); + } + }); + + QUnit.test('dateBox with datetime strategy should be rendered once on init', function(assert) { + const contentReadyHandler = sinon.spy(); + + $('#dateBox').dxDateBox({ + type: 'datetime', + pickerType: 'calendar', + onContentReady: contentReadyHandler + }).dxDateBox('instance'); + + assert.equal(contentReadyHandler.callCount, 1, 'contentReady has been called once'); + }); + + QUnit.test('date box popup should have maximum 100% width', function(assert) { + const currentDevice = sinon.stub(devices, 'current').returns({ + platform: 'generic', + phone: true + }); + + const clock = sinon.useFakeTimers(); + try { + const instance = $('#dateBox').dxDateBox({ + type: 'date', + pickerType: 'rollers', + opened: true + }).dxDateBox('instance'); + + assert.equal(instance._popup.option('maxWidth'), '100%', 'popup width should be correct on 320px screens'); + assert.equal(instance._popup.option('maxHeight'), '100%', 'popup height should be correct on 320px screens'); + } finally { + clock.restore(); + currentDevice.restore(); + } + }); + + QUnit.test('buttons are rendered after \'type\' option was changed', function(assert) { + const $element = $('#dateBox').dxDateBox({ + pickerType: 'calendar', + type: 'datetime', + applyValueMode: 'useButtons' + }); + + const dateBox = $element.dxDateBox('instance'); + + dateBox.open(); + + let $buttons = $('.dx-datebox-wrapper .dx-toolbar .dx-button'); + + assert.equal($buttons.length, 3, 'buttons are rendered'); + + dateBox.option('type', 'date'); + dateBox.open(); + dateBox.option('type', 'datetime'); + dateBox.open(); + + $buttons = $('.dx-datebox-wrapper .dx-toolbar .dx-button'); + assert.equal($buttons.length, 3, 'buttons are rendered after option was changed'); + }); + + QUnit.test('DateBox should have time part when pickerType is rollers', function(assert) { + const date = new Date(2015, 1, 1, 12, 13, 14); + const dateBox = $('#dateBox').dxDateBox({ + pickerType: 'rollers', + type: 'datetime', + value: date + }).dxDateBox('instance'); + + const format = uiDateUtils.FORMATS_MAP['datetime']; + const $input = $(dateBox.$element().find('.' + TEXTEDITOR_INPUT_CLASS)); + + assert.equal($input.val(), dateLocalization.format(date, format), 'input value is correct'); + }); + + QUnit.test('DateBox with pickerType=rollers should scroll to the neighbor item independent of deltaY when device is desktop (T921228)', function(assert) { + const date = new Date(2015, 0, 1); + $('#dateBox').dxDateBox({ + pickerType: 'rollers', + value: date, + opened: true + }); + + const $monthRollerView = $('.dx-dateviewroller-month'); + const monthRollerView = $monthRollerView.dxDateViewRoller('instance'); + const deltaY = 100; + const pointer = pointerMock(monthRollerView.container()); + + assert.strictEqual(monthRollerView.option('selectedIndex'), 0, 'selectedItem is correct'); + + pointer.start().wheel(deltaY).wait(500); + assert.strictEqual(monthRollerView.option('selectedIndex'), 0, 'selectedItem is correct'); + + pointer.start().wheel(-deltaY).wait(500); + assert.strictEqual(monthRollerView.option('selectedIndex'), 1, 'selectedItem is correct'); + + pointer.start().wheel(-deltaY * 3).wait(500); + assert.strictEqual(monthRollerView.option('selectedIndex'), 2, 'selectedItem is correct'); + + pointer.start().wheel(deltaY * 5).wait(500); + assert.strictEqual(monthRollerView.option('selectedIndex'), 1, 'selectedItem is correct'); + + pointer.start().wheel(-deltaY * 10).wait(500); + assert.strictEqual(monthRollerView.option('selectedIndex'), 2, 'selectedItem is correct'); + }); + + QUnit.test('dateview selectedIndex should not be changed after dateBox reopen (T934663)', function(assert) { + assert.expect(0); + + const clock = sinon.useFakeTimers(); + try { + const date = new Date(2015, 3, 3); + const dateBox = $('#dateBox').dxDateBox({ + pickerType: 'rollers', + value: date, + opened: true + }).dxDateBox('instance'); + + const selectedIndexChangedHandler = (args) => { + assert.ok(false, 'selectedIndex has been changed'); + }; + + const monthRollerView = $('.dx-dateviewroller-month').dxDateViewRoller('instance'); + const dayRollerView = $('.dx-dateviewroller-day').dxDateViewRoller('instance'); + const yearRollerView = $('.dx-dateviewroller-year').dxDateViewRoller('instance'); + monthRollerView.option('onSelectedIndexChanged', selectedIndexChangedHandler); + dayRollerView.option('onSelectedIndexChanged', selectedIndexChangedHandler); + yearRollerView.option('onSelectedIndexChanged', selectedIndexChangedHandler); + + dateBox.close(); + dateBox.open(); + } finally { + clock.restore(); + } + }); + + QUnit.test('DateBox with time should be rendered correctly when templatesRenderAsynchronously=true', function(assert) { + const clock = sinon.useFakeTimers(); + try { + const dateBox = $('#dateBox').dxDateBox({ + type: 'datetime', + pickerType: 'calendar', + value: new Date(), + templatesRenderAsynchronously: true + }).dxDateBox('instance'); + + dateBox.option('opened', true); + clock.tick(10); + + const $content = $(dateBox._popup.$content()); + const $timeView = $content.find('.dx-timeview-clock'); + assert.ok($timeView.parent().width() > 100, 'Time view was rendered correctly'); + } finally { + clock.restore(); + } + }); + + QUnit.test('DateBox renders the right stylingMode for editors in time view overlay (default)', function(assert) { + const dateBox = $('#dateBox').dxDateBox({ + type: 'datetime', + value: new Date('2015/1/25') + }).dxDateBox('instance'); + + dateBox.open(); + + const hourEditor = $(`.${TIMEVIEW_CLASS} .${NUMBERBOX_CLASS}`).eq(0); + const minuteEditor = $(`.${TIMEVIEW_CLASS} .${NUMBERBOX_CLASS}`).eq(1); + const amPmEditor = $(`.${TIMEVIEW_CLASS} .${SELECTBOX_CLASS}`).eq(0); + + assert.ok(hourEditor.hasClass('dx-editor-outlined')); + assert.ok(minuteEditor.hasClass('dx-editor-outlined')); + assert.ok(amPmEditor.hasClass('dx-editor-outlined')); + }); + + QUnit.test('DateBox renders the right stylingMode for editors in time view overlay (custom)', function(assert) { + const dateBox = $('#dateBox').dxDateBox({ + type: 'datetime', + value: new Date('2015/1/25'), + stylingMode: 'underlined' + }).dxDateBox('instance'); + + dateBox.open(); + + const hourEditor = $(`.${TIMEVIEW_CLASS} .${NUMBERBOX_CLASS}`).eq(0); + const minuteEditor = $(`.${TIMEVIEW_CLASS} .${NUMBERBOX_CLASS}`).eq(1); + const amPmEditor = $(`.${TIMEVIEW_CLASS} .${SELECTBOX_CLASS}`).eq(0); + + assert.ok(hourEditor.hasClass('dx-editor-underlined')); + assert.ok(minuteEditor.hasClass('dx-editor-underlined')); + assert.ok(amPmEditor.hasClass('dx-editor-underlined')); + }); + + QUnit.test('DateBox with timeview should have amPm popup inside of dateBox popup content (T1300566)', function(assert) { + const dateBox = $('#dateBox').dxDateBox({ + type: 'datetime', + pickerType: 'calendar', + opened: true, + displayFormat: 'ddMMyy hh:mm', + }).dxDateBox('instance'); + const amPmEditor = $(`.${TIMEVIEW_CLASS} .${SELECTBOX_CLASS}`).eq(0).dxSelectBox('instance'); + + amPmEditor.open(); + + const $dateBoxPopup = $(dateBox.content()); + const $amPmPopup = $(amPmEditor.content()); + + assert.strictEqual($amPmPopup.closest($dateBoxPopup).length, 1, 'amPm popup is inside dateBox popup'); + }); + + QUnit.test('Reset seconds and milliseconds when DateBox has no value for time view', function(assert) { + const dateBox = $('#dateBox').dxDateBox({ + pickerType: 'list', + type: 'time' + }).dxDateBox('instance'); + + dateBox.open(); + + $('.dx-list-item').first().trigger('dxclick'); + + assert.equal(dateBox.option('value').getSeconds(), 0, 'seconds has zero value'); + assert.equal(dateBox.option('value').getMilliseconds(), 0, 'milliseconds has zero value'); + }); + + QUnit.module('value change', { + beforeEach: function() { + this.currentDateTime = new Date(2001, 1, 1, 1, 1, 0, 0); + this.clock = sinon.useFakeTimers(this.currentDateTime.valueOf()); + + this.date = new Date('2015/1/25'); + this.init = (options) => { + this.$dateBox = $('#dateBox').dxDateBox($.extend({}, { + type: 'datetime', + value: this.date, + opened: true, + pickerType: 'calendar', + }, options)); + this.dateBox = this.$dateBox.dxDateBox('instance'); + this.$input = this.$dateBox.find(`.${TEXTEDITOR_INPUT_CLASS}`); + this.$submitInput = this.$dateBox.find('input[type=hidden]'); + this.$content = $(this.dateBox.content()); + this.keyboard = keyboardMock(this.$input); + this.timeView = this.$content + .find(`.${TIMEVIEW_CLASS}`) + .dxTimeView('instance'); + this.calendar = this.$content + .find(`.${CALENDAR_CLASS}`) + .dxCalendar('instance'); + + this.$hourBox = this.$content.find(`.${NUMBERBOX_CLASS}`).eq(0); + this.$hourBoxSpinDown = this.$hourBox.find(`.${NUMBERBOX_SPIN_DOWN_CLASS}`).eq(0); + + this.$minuteBox = this.$content.find(`.${NUMBERBOX_CLASS}`).eq(1); + this.$minuteBoxSpinDown = this.$minuteBox.find(`.${NUMBERBOX_SPIN_DOWN_CLASS}`).eq(0); + }; + this.reinit = (options = {}) => { + this.dateBox.dispose(); + this.init(options); + }; + this.clickApplyButton = () => { + $(APPLY_BUTTON_SELECTOR).first().trigger('dxclick'); + }; + this.clickCalendarCell = () => { + $(`.${CALENDAR_CELL_CLASS}`).first().trigger('dxclick'); + }; + + this.init({}); + }, + afterEach: function() { + this.clock.restore(); + } + }, () => { + QUnit.test('dateBox should update time on enter pressing (T969012)', function(assert) { + const expectedDate = new Date(this.date); + expectedDate.setHours(11, 28); + + this.timeView.option('value', expectedDate); + this.keyboard + .focus() + .press('enter'); + + assert.deepEqual(this.dateBox.option('value'), expectedDate, 'dateBox value was updated'); + }); + + QUnit.test('dateBox should update date and time on enter pressing after time value change using arrows', function(assert) { + const time = new Date(this.date); + time.setHours(11, 28); + + this.keyboard.press('up'); + this.timeView.option('value', time); + this.keyboard + .focus() + .press('enter') + .press('enter'); + + const expectedDate = new Date(time); + expectedDate.setDate(18); + + assert.deepEqual(this.dateBox.option('value'), expectedDate, 'dateBox value was updated'); + }); + + QUnit.test('submit value should be updated after apply button click if internal validation is failed', function(assert) { + const date = new Date(new Date('2015/1/25 13:00:00')); + + this.reinit({ + min: date, + value: date + }); + + this.$hourBoxSpinDown.trigger('dxpointerdown'); + this.clickApplyButton(); + + assert.notOk(this.dateBox.option('isValid'), 'editor is invalid'); + assert.strictEqual(this.$submitInput.val(), '2015-01-25T12:00:00', 'submit element has correct value'); + }); + + QUnit.test('submit value should be updated after apply button click if external validation is failed', function(assert) { + this.date = new Date('2015/1/25 13:00:00'); + this.reinit(); + this.$dateBox.dxValidator({ + validationRules: [{ + type: 'custom', + reevaluate: true, + validationCallback: function(e) { + return false; + } + }] + }); + + this.$hourBoxSpinDown.trigger('dxpointerdown'); + this.clickApplyButton(); + + assert.notOk(this.dateBox.option('isValid'), 'editor is invalid'); + assert.strictEqual(this.$submitInput.val(), '2015-01-25T12:00:00', 'submit element has correct value'); + }); + + QUnit.test('invalid (by internal validation) value should be displayed if it is selected by time numberboxes (T939117)', function(assert) { + const date = new Date('2015/1/25 14:00:00'); + this.reinit({ + min: date, + displayFormat: 'HH:mm', + value: date + }); + + this.$hourBoxSpinDown.trigger('dxpointerdown'); + this.clickApplyButton(); + + assert.strictEqual(this.$input.val(), '13:00', 'input displays a correct value'); + assert.strictEqual(this.dateBox.option('text'), '13:00', 'text is invalid'); + assert.notOk(this.dateBox.option('isValid'), 'widget is invalid'); + + assert.deepEqual(this.dateBox.option('value'), new Date(date.setHours(13)), 'value does not changed'); + }); + + QUnit.test('datebox value is bound to time view value', function(assert) { + let date = new Date(2014, 2, 1, 14, 33); + this.dateBox.option('value', date); + assert.equal(this.timeView.option('value').getTime(), date.getTime(), 'timeView value is set'); + + date = new Date(2014, 2, 1, 17, 47); + this.timeView.option('value', date); + + this.clickApplyButton(); + + assert.strictEqual(this.dateBox.option('value').toString(), date.toString(), 'dateBox value is set'); + }); + + QUnit.test('time value should be updated after select date', function(assert) { + this.calendar.option('value', new Date(2014, 2, 1, 11, 15)); + this.timeView.option('value', new Date(2014, 1, 1, 12, 16)); + + this.clickApplyButton(); + + const expectedValue = (new Date(2014, 2, 1, 12, 16)).toString(); + assert.strictEqual(this.dateBox.option('value').toString(), expectedValue, 'dateBox value is set'); + }); + + QUnit.test('Reset seconds and milliseconds when DateBox has no value for datetime view', function(assert) { + this.reinit({ + min: new Date('2015/1/25'), + max: new Date('2015/2/10') + }); + + this.clickCalendarCell(); + this.clickApplyButton(); + + assert.strictEqual(this.dateBox.option('value').getSeconds(), 0, 'seconds has zero value'); + assert.strictEqual(this.dateBox.option('value').getMilliseconds(), 0, 'milliseconds has zero value'); + }); + + QUnit.test('time is reset when calendar value is changed (T208853)', function(assert) { + this.date = new Date(2015, 1, 16, 11, 20); + this.reinit(); + + this.calendar.option('value', new Date(2014, 1, 16)); + this.clickApplyButton(); + + assert.deepEqual(this.dateBox.option('value'), new Date(2014, 1, 16, 11, 20), 'date and time are correct'); + }); + + ['instantly', 'useButtons'].forEach(applyValueMode => { + QUnit.module(`the out of range value, applyValueMode: ${applyValueMode}`, { + beforeEach: function() { + this.date = new Date('2015/1/25 14:00:00'); + this.onValueChangedHandler = sinon.stub(); + + this.reinit({ + type: 'datetime', + min: this.date, + max: this.date, + value: this.date, + applyValueMode, + useMaskBehavior: true, + onValueChanged: this.onValueChangedHandler + }); + }, + afterEach: function() { + this.onValueChangedHandler.reset(); + } + }, () => { + QUnit.test('valueChangeEvent should be called after change of datebox value', function(assert) { + this.dateBox.option('value', new Date('2015/1/25 13:00:00')); + + assert.strictEqual(this.onValueChangedHandler.callCount, applyValueMode === 'instantly' ? 3 : 1, 'onValueChangedHandler.callCount'); + assert.strictEqual(this.$input.val(), '1/25/2015, 1:00 PM', 'input displays a correct value'); + + const { text, isValid, value } = this.dateBox.option(); + assert.strictEqual(text, '1/25/2015, 1:00 PM', 'text is right'); + assert.strictEqual(isValid, false, 'widget is invalid'); + assert.deepEqual(value, new Date(this.date.setHours(13)), 'value is changed correctly'); + }); + + QUnit.test('valueChangeEvent should be called after change of hours by spin click', function(assert) { + this.$hourBoxSpinDown.trigger('dxpointerdown'); + this.clickApplyButton(); + + assert.strictEqual(this.onValueChangedHandler.callCount, 1, 'onValueChangedHandler.callCount'); + assert.strictEqual(this.$input.val(), '1/25/2015, 1:00 PM', 'input displays a correct value'); + + const { text, isValid, value } = this.dateBox.option(); + assert.strictEqual(text, '1/25/2015, 1:00 PM', 'text is right'); + assert.strictEqual(isValid, false, 'widget is invalid'); + assert.deepEqual(value, new Date(this.date.setHours(13)), 'value is changed correctly'); + }); + + QUnit.test('valueChangeEvent should be called after change of minutes by spin click', function(assert) { + this.$minuteBoxSpinDown.trigger('dxpointerdown'); + this.clickApplyButton(); + + assert.strictEqual(this.onValueChangedHandler.callCount, 1, 'onValueChangedHandler.callCount'); + assert.strictEqual(this.$input.val(), '1/25/2015, 2:59 PM', 'input displays a correct value'); + + const { text, isValid, value } = this.dateBox.option(); + assert.strictEqual(text, '1/25/2015, 2:59 PM', 'text is right'); + assert.strictEqual(isValid, false, 'widget is invalid'); + assert.deepEqual(value, new Date(this.date.setMinutes(59)), 'value is changed correctly'); + }); + + QUnit.test('valueChangeEvent should be called after input in hours', function(assert) { + const keyboard = keyboardMock(this.$hourBox.find(`.${TEXTEDITOR_INPUT_CLASS}`)); + keyboard + .focus() + .caret(this.$input.val().length - 3) + .press('backspace') + .press('backspace') + .type('13') + .change(); + + if(applyValueMode === 'useButtons') { + this.clickApplyButton(); + } + + assert.strictEqual(this.onValueChangedHandler.callCount, 1, 'onValueChangedHandler.callCount'); + assert.strictEqual(this.$input.val(), '1/25/2015, 1:00 PM', 'input displays a correct value'); + + const { text, isValid, value } = this.dateBox.option(); + assert.strictEqual(text, '1/25/2015, 1:00 PM', 'text is right'); + assert.strictEqual(isValid, false, 'widget is invalid'); + assert.deepEqual(value, new Date(this.date.setHours(13)), 'value is changed correctly'); + }); + + QUnit.test('valueChangeEvent should be called after input in minutes', function(assert) { + const keyboard = keyboardMock(this.$minuteBox.find(`.${TEXTEDITOR_INPUT_CLASS}`)); + keyboard + .focus() + .caret(this.$input.val().length - 3) + .press('backspace') + .press('backspace') + .type('59') + .change(); + + if(applyValueMode === 'useButtons') { + this.clickApplyButton(); + } + + assert.strictEqual(this.onValueChangedHandler.callCount, 1, 'onValueChangedHandler.callCount'); + assert.strictEqual(this.$input.val(), '1/25/2015, 2:59 PM', 'input displays a correct value'); + + const { text, isValid, value } = this.dateBox.option(); + assert.strictEqual(text, '1/25/2015, 2:59 PM', 'text is right'); + assert.strictEqual(isValid, false, 'widget is invalid'); + assert.deepEqual(value, new Date(this.date.setMinutes(59)), 'value is changed correctly'); + }); + }); + }); + + QUnit.module('partial datetime selecting when value=null', { + beforeEach: function() { + this.reinit({ + value: null + }); + } + }, () => { + QUnit.test('updated value time should be equal to current time if only date is selected (T231015)', function(assert) { + const newDateTime = new Date(2002, 2, 2, 1, 1, 0, 0); + + this.calendar.option('value', new Date(2002, 2, 2, 14, 17, 22, 34)); // updates only date + this.clickApplyButton(); + + assert.strictEqual(this.dateBox.option('value').getTime(), newDateTime.getTime(), 'value is correct if only calendar value is changed'); + }); + + QUnit.test('updated value time should be equal to selected time if only time is selected (T231015)', function(assert) { + const newDateTime = new Date(2001, 1, 1, 2, 2, 0, 0); + + this.timeView.option('value', new Date(2002, 2, 2, 2, 2)); // updated only time + this.clickApplyButton(); + + assert.strictEqual(this.dateBox.option('value').getTime(), newDateTime.getTime(), 'value is correct if only timeView value is changed'); + }); + + QUnit.test('updated value should have date and time equal to current date and time after apply button click if value is null (T253298)', function(assert) { + this.clickApplyButton(); + + assert.strictEqual(this.dateBox.option('value').getDate(), this.currentDateTime.getDate(), 'value date is correct'); + assert.strictEqual(this.dateBox.option('value').getTime(), this.currentDateTime.getTime(), 'value time is correct'); + }); + + QUnit.test('updated value should have date equal to calendar controured date after apply button click if value is null (T1039021)', function(assert) { + const minDate = new Date(2050, 10, 10); + this.reinit({ + value: null, + min: minDate + }); + + this.clickApplyButton(); + + const expectedDateTime = new Date(minDate); + expectedDateTime.setHours(this.currentDateTime.getHours(), this.currentDateTime.getMinutes()); + + assert.strictEqual(this.dateBox.option('value').getDate(), expectedDateTime.getDate(), 'value date is correct'); + assert.strictEqual(this.dateBox.option('value').getTime(), expectedDateTime.getTime(), 'value time is correct'); + }); + }); + }); +}); + +QUnit.module('datebox w/ time list', { + before: function() { + this.checkForIncorrectKeyWarning = function(assert) { + const isIncorrectKeyWarning = logger.warn.lastCall.args[0].indexOf('W1002') > -1; + assert.ok(logger.warn.calledOnce); + assert.ok(isIncorrectKeyWarning); + }; + }, + beforeEach: function() { + fx.off = true; + + this.$dateBox = $('#dateBox'); + + this.dateBox = this.$dateBox + .dxDateBox({ + pickerType: 'list', + type: 'time' + }) + .dxDateBox('instance'); + }, + afterEach: function() { + fx.off = false; + } +}, () => { + QUnit.test('rendered markup', function(assert) { + this.dateBox.option('opened', true); + assert.ok($(DATEBOX_LIST_POPUP_SELECTOR).length, 'Popup has dx-timebox-popup-wrapper class'); + }); + + QUnit.test('rendered popup markup', function(assert) { + this.dateBox.option('opened', true); + + assert.ok(this.dateBox._popup, 'popup exist'); + }); + + QUnit.test('rendered list markup', function(assert) { + this.dateBox.option('opened', true); + + assert.ok(getInstanceWidget(this.dateBox), 'list exist'); + assert.ok(getInstanceWidget(this.dateBox).$element().hasClass(LIST_CLASS), 'list initialized'); + }); + + QUnit.test('list should contain correct values if min/max does not specified', function(assert) { + this.dateBox.option({ + min: null, + max: null + }); + + this.dateBox.option('opened', true); + + const $timeList = $(`.${LIST_CLASS}`); + const $listItems = $timeList.find('.dx-list-item-content'); + + assert.equal($listItems.first().text(), '12:00 AM', 'min value is right'); + assert.equal($listItems.last().text(), '11:30 PM', 'max value is right'); + }); + + QUnit.test('list should contain all correct values when min/max options are defined (T869203)', function(assert) { + this.dateBox.option({ + min: new Date(2015, 11, 1, 5, 45), + max: new Date(2015, 11, 1, 6, 15), + interval: 15 + }); + + this.dateBox.option('opened', true); + + const $timeList = $(`.${LIST_CLASS}`); + const $listItems = $timeList.find('.dx-list-item-content'); + + assert.strictEqual($listItems.first().text(), '5:45 AM', 'min value is right'); + assert.strictEqual($listItems.last().text(), '6:15 AM', 'max value is right'); + assert.strictEqual($listItems.length, 3, 'list items count is correct'); + }); + + QUnit.test('min/max option test', function(assert) { + this.dateBox.option({ + min: new Date(2008, 7, 8, 4, 0), + max: new Date(2008, 7, 8, 8, 59) + }); + + this.dateBox.option('opened', true); + + const $timeList = $(`.${LIST_CLASS}`); + const $listItems = $timeList.find('.dx-list-item-content'); + + assert.equal($listItems.first().text(), '4:00 AM', 'min value is right'); + assert.equal($listItems.last().text(), '8:30 AM', 'max value is right'); + }); + + QUnit.test('min/max overflow test', function(assert) { + this.dateBox.option({ + min: new Date(2008, 7, 8, 4, 0), + max: new Date(2008, 7, 9, 9, 0) + }); + + this.dateBox.option('opened', true); + + const $timeList = $(`.${LIST_CLASS}`); + const $listItems = $timeList.find('.dx-list-item-content'); + + assert.strictEqual($listItems.first().text(), '4:00 AM', 'min value is right'); + assert.strictEqual($listItems.last().text(), '4:00 AM', 'max value is right'); + }); + + QUnit.test('interval option', function(assert) { + this.dateBox.option({ + min: new Date(2008, 7, 8, 4, 0), + value: new Date(2008, 7, 8, 5, 0), + max: new Date(2008, 7, 8, 6, 0), + interval: 60 + }); + + this.dateBox.option('opened', true); + + let $timeList = $(`.${LIST_CLASS}`); + let items = $timeList.find(LIST_ITEM_SELECTOR); + + assert.strictEqual(items.length, 3, 'interval option works'); + + this.dateBox.option('interval', 120); + this.dateBox.option('opened', true); + + $timeList = $(`.${LIST_CLASS}`); + items = $timeList.find(LIST_ITEM_SELECTOR); + + assert.strictEqual(items.length, 2, 'interval option works'); + }); + + QUnit.test('T240639 - correct list item should be highlighted if appropriate datebox value is set', function(assert) { + sinon.stub(logger, 'warn'); + try { + this.dateBox.option({ + type: 'time', + pickerType: 'list', + value: new Date(0, 0, 0, 12, 30), + opened: true + }); + + const list = this.dateBox._strategy._widget; + + assert.deepEqual(list.option('selectedIndex'), 25, 'selectedIndex item is correct'); + assert.deepEqual(list.option('selectedItem'), new Date(0, 0, 0, 12, 30), 'selected list item is correct'); + + this.dateBox.option('value', new Date(2016, 1, 1, 12, 20)); + + this.checkForIncorrectKeyWarning(assert); + assert.equal(list.option('selectedIndex'), -1, 'there is no selected list item'); + assert.equal(list.option('selectedItem'), null, 'there is no selected list item'); + } finally { + logger.warn.restore(); + } + }); + + QUnit.test('T351678 - the date is reset after item click', function(assert) { + this.dateBox.option({ + type: 'time', + pickerType: 'list', + value: new Date(2020, 4, 13, 12, 17), + opened: true + }); + + const $list = $(this.dateBox._strategy._widget.$element()); + $($list.find('.dx-list-item').eq(3)).trigger('dxclick'); + + assert.deepEqual(this.dateBox.option('value'), new Date(2020, 4, 13, 1, 30), 'date is correct'); + }); + + QUnit.test('the date should be in range after the selection', function(assert) { + this.dateBox.option({ + type: 'time', + pickerType: 'list', + min: new Date(2016, 10, 5, 12, 0, 0), + max: new Date(2016, 10, 5, 14, 0, 0), + opened: true + }); + + const $item = $(this.dateBox.content()).find('.dx-list-item').eq(0); + + $item.trigger('dxclick'); + + assert.deepEqual(this.dateBox.option('value'), new Date(2016, 10, 5, 12, 0, 0), 'date is correct'); + }); + + QUnit.test('list should have items if the \'min\' option is specified (T395529)', function(assert) { + this.dateBox.option({ + min: new Date(new Date(null).setHours(15)), + opened: true + }); + + const list = $(`.${LIST_CLASS}`).dxList('instance'); + assert.ok(list.option('items').length > 0, 'list is not empty'); + }); + + QUnit.test('selected date should be in 1970 when it was set from the null value', function(assert) { + this.dateBox.option({ + opened: true, + value: null + }); + + const $item = $(this.dateBox.content()).find('.dx-list-item').eq(0); + $item.trigger('dxclick'); + + assert.strictEqual(this.dateBox.option('value').getFullYear(), new Date(null).getFullYear(), 'year is correct'); + }); + + QUnit.test('selected date should be in value year when value is specified', function(assert) { + this.dateBox.option({ + opened: true, + value: new Date(2018, 5, 6, 14, 12) + }); + + const $item = $(this.dateBox.content()).find('.dx-list-item').eq(0); + $item.trigger('dxclick'); + + assert.strictEqual(this.dateBox.option('value').getFullYear(), 2018, 'year is correct'); + }); + + QUnit.test('selected date should be in 1970 when it was set from user\'s input', function(assert) { + this.dateBox.option({ + value: null, + displayFormat: 'HH:mm' + }); + + keyboardMock(this.$dateBox.find(`.${TEXTEDITOR_INPUT_CLASS}`)) + .focus() + .type('11:11') + .change(); + + assert.strictEqual(this.dateBox.option('value').getFullYear(), new Date(null).getFullYear(), 'year is correct'); + }); + + QUnit.test('the value\'s date part should not be changed if editing input\'s text by keyboard (T395685)', function(assert) { + this.dateBox.option({ + focusStateEnabled: true, + value: new Date(2016, 5, 25, 14, 22) + }); + + const $input = this.$dateBox.find('.' + TEXTEDITOR_INPUT_CLASS); + keyboardMock($input) + .focus() + .caret($input.val().length - 3) + .press('backspace') + .press('backspace') + .type('44') + .change(); + + assert.deepEqual(this.dateBox.option('value'), new Date(2016, 5, 25, 14, 44), 'value is correct'); + }); + + QUnit.test('List of items should be refreshed after value is changed', function(assert) { + this.dateBox.option({ + min: new Date(2016, 1, 1, 10, 0), + value: new Date(2016, 1, 2, 14, 45), + interval: 60, + opened: true + }); + + const $timeList = $(`.${LIST_CLASS}`); + let items = $timeList.find(LIST_ITEM_SELECTOR); + + assert.equal(items.length, 24, '24 items should be find'); + + this.dateBox.option('value', new Date(2016, 1, 1)); + + items = $timeList.find(LIST_ITEM_SELECTOR); + + assert.equal(items.length, 14, '14 items should be find from min to finish of day'); + }); + + QUnit.test('All items in list should be present if value and min options are belong to different days', function(assert) { + const clock = sinon.useFakeTimers(); + sinon.stub(logger, 'warn'); + try { + this.dateBox.option({ + min: new Date(2016, 1, 1, 13, 45), + value: new Date(2016, 1, 1, 14, 45), + interval: 60, + opened: true + }); + + const $timeList = $(`.${LIST_CLASS}`); + let items = $timeList.find(LIST_ITEM_SELECTOR); + + assert.equal(items.length, 11, 'interval option works'); + + this.dateBox.option('value', new Date(2016, 1, 2, 13, 45)); + + items = $timeList.find(LIST_ITEM_SELECTOR); + + this.checkForIncorrectKeyWarning(assert); + assert.equal(items.length, 24, 'interval is correct'); + assert.equal(items.eq(0).text(), '12:45 AM', 'start time is correct'); + } finally { + clock.restore(); + logger.warn.restore(); + } + }); + + QUnit.test('The situation when value and max options are belong to one day', function(assert) { + this.dateBox.option({ + value: new Date(2016, 1, 1, 13, 45), + max: new Date(2016, 1, 1, 15, 0), + interval: 60, + opened: true + }); + + const $timeList = $(`.${LIST_CLASS}`); + const items = $timeList.find(LIST_ITEM_SELECTOR); + + assert.strictEqual(items.length, 16, 'list should be contain right count of items'); + }); + + QUnit.test('value and max are belong to one day', function(assert) { + this.dateBox.option({ + min: new Date(2016, 1, 1, 0, 11), + value: new Date(2016, 1, 3, 14, 45), + max: new Date(2016, 1, 3, 18, 22), + interval: 60, + opened: true + }); + + const $timeList = $(`.${LIST_CLASS}`); + const items = $timeList.find(LIST_ITEM_SELECTOR); + + assert.equal(items.length, 19, 'list should be contain right count of items'); + assert.equal(items.eq(0).text(), '12:11 AM', 'first item in list is correct'); + assert.equal(items.eq(items.length - 1).text(), '6:11 PM', 'last item in list is correct'); + }); + + QUnit.test('List items should be started with minimal possible value', function(assert) { + this.dateBox.option({ + min: new Date(2016, 1, 1, 0, 17), + value: new Date(2016, 1, 3, 14, 45), + interval: 15, + opened: true + }); + + const $timeList = $(`.${LIST_CLASS}`); + const items = $timeList.find(LIST_ITEM_SELECTOR); + + assert.equal(items.eq(0).text(), '12:02 AM', 'first item in list is correct'); + assert.equal(items.eq(items.length - 1).text(), '11:47 PM', 'last item in list is correct'); + }); + + QUnit.test('dxDateBox with list strategy automatically scrolls to selected item on opening', function(assert) { + this.dateBox.option({ + value: new Date(2016, 1, 3, 14, 45), + interval: 15, + opened: true + }); + + this.dateBox.option('opened', true); + + const $popupContent = $('.dx-popup-content'); + const $selectedItem = $popupContent.find('.' + LIST_ITEM_SELECTED_CLASS); + + assert.ok($popupContent.offset().top + $popupContent.height() > $selectedItem.offset().top, 'selected item is visible'); + }); + + QUnit.test('min/max settings should be work if value option is null', function(assert) { + this.dateBox.option({ + value: null, + min: new Date(2008, 7, 8, 8, 0), + max: new Date(2008, 7, 8, 20, 0) + }); + + this.dateBox.option('opened', true); + + const $timeList = $(`.${LIST_CLASS}`); + const $listItems = $timeList.find('.dx-list-item-content'); + + assert.strictEqual($listItems.first().text(), '8:00 AM', 'min value is right'); + assert.strictEqual($listItems.last().text(), '8:00 PM', 'max value is right'); + }); + + QUnit.test('min/max settings should be work if value option is undefined', function(assert) { + this.dateBox.option({ + value: undefined, + min: new Date(2008, 7, 8, 8, 0), + max: new Date(2008, 7, 8, 20, 0) + }); + + this.dateBox.option('opened', true); + + const $timeList = $(`.${LIST_CLASS}`); + const $listItems = $timeList.find('.dx-list-item-content'); + + assert.strictEqual($listItems.first().text(), '8:00 AM', 'min value is right'); + assert.strictEqual($listItems.last().text(), '8:00 PM', 'max value is right'); + }); + + QUnit.test('validator correctly check value with \'time\' format', function(assert) { + sinon.stub(logger, 'warn'); + try { + const $dateBox = $('#dateBox').dxDateBox({ + type: 'time', + pickerType: 'list', + min: new Date(2015, 1, 1, 6, 0), + max: new Date(2015, 1, 1, 16, 0), + value: new Date(2015, 1, 1, 12, 0), + opened: true + }); + + const dateBox = $dateBox.dxDateBox('instance'); + const $input = $dateBox.find('.' + TEXTEDITOR_INPUT_CLASS); + + $input.val('11:30 AM').change(); + + const value = dateBox.option('value'); + this.checkForIncorrectKeyWarning(assert); + assert.equal($input.val(), '11:30 AM', 'Correct input value'); + assert.equal(value.getHours(), 11, 'Correct hours'); + assert.equal(value.getMinutes(), 30, 'Correct minutes'); + assert.equal(dateBox.option('isValid'), true, 'Editor should be marked as valid'); + } finally { + logger.warn.restore(); + } + }); + + QUnit.testInActiveWindow('select a new value via the Enter key', function(assert) { + const $dateBox = $('#dateBox').dxDateBox({ + type: 'time', + value: new Date(2018, 2, 2, 12, 0, 13), + pickerType: 'list' + }); + + const dateBox = $dateBox.dxDateBox('instance'); + const $input = $dateBox.find('.' + TEXTEDITOR_INPUT_CLASS); + const keyboard = keyboardMock($input); + + $input.focusin(); + this.dateBox.option('opened', true); + keyboard + .keyDown('down') + .keyDown('down') + .keyDown('enter'); + + const value = dateBox.option('value'); + assert.equal($input.val(), '1:00 PM', 'Correct input value'); + assert.equal(value.getHours(), 13, 'Correct hours'); + assert.equal(value.getMinutes(), 0, 'Correct minutes'); + }); + + QUnit.test('items are rendered when value is \'undefined\' (T805931)', function(assert) { + this.dateBox.option({ + value: undefined + }); + + this.dateBox.option('opened', true); + + const $timeListItems = $('.dx-list .dx-list-item'); + assert.ok($timeListItems.length > 0); + }); + + QUnit.test('should works correctly with serialized dates (T854579)', function(assert) { + this.dateBox.option({ + opened: true, + dateSerializationFormat: 'yyyy-MM-ddTHH:mm:ssx', + }); + const $input = $(this.dateBox.element()).find(`.${TEXTEDITOR_INPUT_CLASS}`); + const $items = $(this.dateBox.content()).find(LIST_ITEM_SELECTOR); + + $items.eq(1).trigger('dxclick'); + assert.strictEqual($input.val(), $items.eq(1).text(), 'time is applied'); + + this.dateBox.open(); + $items.eq(3).trigger('dxclick'); + assert.strictEqual($input.val(), $items.eq(3).text(), 'new time is applied'); + }); + + QUnit.module('applyValueMode = useButtons', { + beforeEach: function() { + this.date = new Date(2020, 1, 1); + this.dateBox.option({ + value: this.date, + opened: true, + applyValueMode: 'useButtons' + }); + this.$items = $(this.dateBox.content()).find(LIST_ITEM_SELECTOR); + this.$firstItem = this.$items.eq(1); + } + }, () => { + QUnit.test('should not close popup on list item click', function(assert) { + this.$firstItem.trigger('dxclick'); + + assert.ok(this.dateBox.option('opened'), 'dateBox is still opened'); + }); + + QUnit.test('should not instantly select value on list item click (T1005111)', function(assert) { + this.$firstItem.trigger('dxclick'); + + assert.deepEqual(this.dateBox.option('value'), this.date, 'item is not selected'); + }); + + QUnit.test('should not raise validation error on "Ok" button click without item selecting (T1005111)', function(assert) { + $(APPLY_BUTTON_SELECTOR).trigger('dxclick'); + + assert.ok(this.dateBox.option('isValid'), 'dateBox is still valid'); + }); + + QUnit.test('should update value on "Ok" button click', function(assert) { + const expectedDate = new Date(this.date); + expectedDate.setHours(0, 30); + this.$firstItem.trigger('dxclick'); + $(APPLY_BUTTON_SELECTOR).trigger('dxclick'); + + assert.deepEqual(this.dateBox.option('value'), expectedDate, 'value is updated'); + }); + + QUnit.test('should not update value on "Cancel" button click', function(assert) { + this.$firstItem.trigger('dxclick'); + $(CANCEL_BUTTON_SELECTOR).trigger('dxclick'); + + assert.deepEqual(this.dateBox.option('value'), this.date, 'value is not updated'); + }); + + QUnit.testInActiveWindow('should not close on "tab" press', function(assert) { + const $input = this.$dateBox.find(`.${TEXTEDITOR_INPUT_CLASS}`); + const keyboard = keyboardMock($input); + + keyboard + .focus() + .keyDown('tab'); + + assert.ok(this.dateBox.option('opened'), 'dateBox is still opened'); + }); + }); +}); + + +QUnit.module('width of datebox with list', { + beforeEach: function() { + fx.off = true; + + this.$dateBox = $('#dateBox'); + }, + afterEach: function() { + fx.off = false; + } +}, () => { + QUnit.module('overlay content real width', () => { + QUnit.test('should be equal to the editor width when dropDownOptions.width in not defined', function(assert) { + this.$dateBox.dxDateBox({ + opened: true, + pickerType: 'list', + type: 'time' + }).dxDateBox('instance'); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + assert.strictEqual($overlayContent.outerWidth(), this.$dateBox.outerWidth(), 'popup width is correct'); + }); + + QUnit.test('should be equal to the editor width when dropDownOptions.width in not defined after editor width runtime change', function(assert) { + const dateBox = this.$dateBox.dxDateBox({ + opened: true, + pickerType: 'list', + type: 'time' + }).dxDateBox('instance'); + + dateBox.option('width', 153); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + assert.strictEqual($overlayContent.outerWidth(), this.$dateBox.outerWidth(), 'popup width is correct'); + }); + + QUnit.test('should be equal to dropDownOptions.width if it\'s defined (T897820)', function(assert) { + this.$dateBox.dxDateBox({ + type: 'time', + pickerType: 'list', + dropDownOptions: { + width: 500 + }, + opened: true + }); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + assert.strictEqual($overlayContent.outerWidth(), 500, 'overlay content width is correct'); + }); + + QUnit.test('should be equal to dropDownOptions.width even after editor input width change (T897820)', function(assert) { + const dateBox = this.$dateBox.dxDateBox({ + type: 'time', + pickerType: 'list', + dropDownOptions: { + width: 500 + }, + opened: true + }).dxDateBox('instance'); + + dateBox.option('width', 300); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + assert.strictEqual($overlayContent.outerWidth(), 500, 'overlay content width is correct'); + }); + + QUnit.test('should be equal to wrapper width if dropDownOptions.width is set to auto (T897820)', function(assert) { + this.$dateBox.dxDateBox({ + type: 'time', + pickerType: 'list', + dropDownOptions: { + width: 'auto' + }, + opened: true + }); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + const $overlayWrapper = $(`.${OVERLAY_WRAPPER_CLASS}`); + assert.strictEqual($overlayContent.outerWidth(), $overlayWrapper.outerWidth(), 'overlay content width is correct'); + }); + + QUnit.test('should be equal to wrapper width if dropDownOptions.width is set to 100%', function(assert) { + this.$dateBox.dxDateBox({ + type: 'time', + pickerType: 'list', + dropDownOptions: { + width: '100%' + }, + opened: true + }); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + const $overlayWrapper = $(`.${OVERLAY_WRAPPER_CLASS}`); + assert.strictEqual($overlayContent.outerWidth(), $overlayWrapper.outerWidth(), 'overlay content width is correct'); + }); + + QUnit.test('should be calculated relative to wrapper when dropDownOptions.width is percent (T897820)', function(assert) { + this.$dateBox.dxDateBox({ + type: 'time', + pickerType: 'list', + dropDownOptions: { + width: '50%' + }, + opened: true + }); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + const $overlayWrapper = $(`.${OVERLAY_WRAPPER_CLASS}`); + assert.roughEqual($overlayContent.outerWidth(), $overlayWrapper.outerWidth() / 2, 0.1, 'overlay content width is correct'); + }); + + QUnit.test('should be calculated relative to wrapper after editor width runtime change', function(assert) { + const dateBox = this.$dateBox.dxDateBox({ + type: 'time', + pickerType: 'list', + width: 600, + dropDownOptions: { + width: '50%' + }, + opened: true + }).dxDateBox('instance'); + + dateBox.option('width', 700); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + const $overlayWrapper = $(`.${OVERLAY_WRAPPER_CLASS}`); + assert.roughEqual($overlayContent.outerWidth(), $overlayWrapper.outerWidth() / 2, 0.1, 'overlay content width is correct'); + }); + + QUnit.test('should be equal to editor input width even when dropDownOptions.container is defined (T938497)', function(assert) { + this.$dateBox.dxDateBox({ + type: 'time', + pickerType: 'list', + dropDownOptions: { + container: $('#containerWithWidth') + }, + opened: true + }); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + + assert.strictEqual($overlayContent.outerWidth(), this.$dateBox.outerWidth(), 'width is correct'); + }); + }); + + QUnit.test('dropDownOptions.width should be passed to popup', function(assert) { + this.$dateBox.dxDateBox({ + type: 'time', + pickerType: 'list', + dropDownOptions: { + width: 500 + }, + opened: true + }); + + const popup = this.$dateBox.find(`.${POPUP_CLASS}`).dxPopup('instance'); + assert.strictEqual(popup.option('width'), 500, 'popup width option value is correct'); + }); + + QUnit.test('popup should have width equal to dropDownOptions.width even after editor input width change (T897820)', function(assert) { + const dateBox = this.$dateBox.dxDateBox({ + type: 'time', + pickerType: 'list', + dropDownOptions: { + width: 500 + }, + opened: true + }).dxDateBox('instance'); + + dateBox.option('width', 300); + + const popup = this.$dateBox.find(`.${POPUP_CLASS}`).dxPopup('instance'); + assert.strictEqual(popup.option('width'), 500, 'popup width option value is correct'); + }); +}); + +QUnit.module('height of datebox with list', { + beforeEach: function() { + fx.off = true; + + this.$dateBox = $('#dateBox'); + }, + afterEach: function() { + fx.off = false; + } +}, () => { + QUnit.module('overlay content height', () => { + QUnit.test('should be equal to 0.45 * window height when dropDownOptions.height in not defined and list hight is bigger than 0.45 of window height', function(assert) { + this.$dateBox.dxDateBox({ + opened: true, + pickerType: 'list', + type: 'time' + }).dxDateBox('instance'); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + assert.roughEqual($overlayContent.outerHeight(), 0.45 * $(window).outerHeight(), 0.1, 'overlay content height is correct'); + }); + + QUnit.test('should be equal to 0.45 * window height when dropDownOptions.height in set to auto and list hight is bigger than 0.45 of window height', function(assert) { + this.$dateBox.dxDateBox({ + opened: true, + pickerType: 'list', + type: 'time', + dropDownOptions: { + height: 'auto' + } + }).dxDateBox('instance'); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + assert.roughEqual($overlayContent.outerHeight(), 0.45 * $(window).outerHeight(), 0.1, 'overlay content height is correct'); + }); + + QUnit.test('should be equal to 0.45 * window height when dropDownOptions.height in not defined after editor height runtime change', function(assert) { + const dateBox = this.$dateBox.dxDateBox({ + opened: true, + pickerType: 'list', + type: 'time' + }).dxDateBox('instance'); + + dateBox.option('height', 153); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + assert.roughEqual($overlayContent.outerHeight(), 0.45 * $(window).outerHeight(), 0.1, 'overlay content height is correct'); + }); + + QUnit.test('should be equal to list height when dropDownOptions.height in not defined and content list is smaller than 0.45 of window height', function(assert) { + this.$dateBox.dxDateBox({ + opened: true, + pickerType: 'list', + type: 'time', + min: Date.now(), + max: Date.now(), + }).dxDateBox('instance'); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + const $list = $(`.${LIST_CLASS}`); + assert.strictEqual($overlayContent.outerHeight(), $list.outerHeight(), 'overlay content height is correct'); + }); + + QUnit.test('should be equal to list height when dropDownOptions.height in set to auto and content list is smaller than 0.45 of window height', function(assert) { + this.$dateBox.dxDateBox({ + opened: true, + pickerType: 'list', + type: 'time', + min: Date.now(), + max: Date.now(), + dropDownOptions: { + height: 'auto' + } + }).dxDateBox('instance'); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + const $list = $(`.${LIST_CLASS}`); + assert.strictEqual($overlayContent.outerHeight(), $list.outerHeight(), 'overlay content height is correct'); + }); + + QUnit.test('should be equal to dropDownOptions.height if it is defined', function(assert) { + const dropDownOptionsHeight = 200; + this.$dateBox.dxDateBox({ + type: 'time', + pickerType: 'list', + dropDownOptions: { + height: dropDownOptionsHeight + }, + opened: true + }); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + assert.strictEqual($overlayContent.outerHeight(), dropDownOptionsHeight, 'overlay content height is correct'); + }); + + QUnit.test('should be equal to dropDownOptions.height even after editor input height change', function(assert) { + const dropDownOptionsHeight = 500; + const dateBox = this.$dateBox.dxDateBox({ + type: 'time', + pickerType: 'list', + dropDownOptions: { + height: dropDownOptionsHeight + }, + opened: true + }).dxDateBox('instance'); + + dateBox.option('height', 300); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + assert.strictEqual($overlayContent.outerHeight(), dropDownOptionsHeight, 'overlay content height is correct'); + }); + + QUnit.test('should be equal to wrapper height if dropDownOptions.height is set to 100%', function(assert) { + this.$dateBox.dxDateBox({ + type: 'time', + pickerType: 'list', + dropDownOptions: { + height: '100%' + }, + opened: true + }); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + const $overlayWrapper = $(`.${OVERLAY_WRAPPER_CLASS}`); + assert.strictEqual($overlayContent.outerHeight(), $overlayWrapper.outerHeight(), 'overlay content height is correct'); + }); + + QUnit.test('should be calculated relative to wrapper when dropDownOptions.height is percent', function(assert) { + this.$dateBox.dxDateBox({ + type: 'time', + pickerType: 'list', + dropDownOptions: { + height: '50%' + }, + opened: true + }); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + const $overlayWrapper = $(`.${OVERLAY_WRAPPER_CLASS}`); + assert.roughEqual($overlayContent.outerHeight(), $overlayWrapper.outerHeight() / 2, 0.1, 'overlay content height is correct'); + }); + + QUnit.test('should be calculated relative to wrapper after editor height runtime change', function(assert) { + const dateBox = this.$dateBox.dxDateBox({ + type: 'time', + pickerType: 'list', + height: 600, + dropDownOptions: { + height: '50%' + }, + opened: true + }).dxDateBox('instance'); + + dateBox.option('height', 700); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + const $overlayWrapper = $(`.${OVERLAY_WRAPPER_CLASS}`); + assert.roughEqual($overlayContent.outerHeight(), $overlayWrapper.outerHeight() / 2, 0.1, 'overlay content height is correct'); + }); + }); + + QUnit.test('dropDownOptions.height should be passed to popup', function(assert) { + const dropDownOptionsHeight = 500; + this.$dateBox.dxDateBox({ + type: 'time', + pickerType: 'list', + dropDownOptions: { + height: dropDownOptionsHeight + }, + opened: true + }); + + const popup = this.$dateBox.find(`.${POPUP_CLASS}`).dxPopup('instance'); + assert.strictEqual(popup.option('height'), dropDownOptionsHeight, 'popup height option value is correct'); + }); + + QUnit.test('popup should have height equal to dropDownOptions.height even after editor input height change', function(assert) { + const dropDownOptionsHeight = 500; + const dateBox = this.$dateBox.dxDateBox({ + type: 'time', + pickerType: 'list', + dropDownOptions: { + height: dropDownOptionsHeight + }, + opened: true + }).dxDateBox('instance'); + + dateBox.option('height', 300); + + const popup = this.$dateBox.find(`.${POPUP_CLASS}`).dxPopup('instance'); + assert.strictEqual(popup.option('height'), dropDownOptionsHeight, 'popup height option value is correct'); + }); +}); + +QUnit.module('width of datebox with calendar', { + beforeEach: function() { + fx.off = true; + + this.$dateBox = $('#dateBox'); + }, + afterEach: function() { + fx.off = false; + } +}, () => { + QUnit.module('overlay content width', () => { + QUnit.test('should be equal to the calendar width + margins when dropDownOptions.width in not defined', function(assert) { + this.$dateBox.dxDateBox({ + opened: true, + pickerType: 'calendar' + }).dxDateBox('instance'); + + const $calendar = $(`.${CALENDAR_CLASS}`); + const paddingsWidth = parseInt($calendar.css('margin-left')) * 2; + const calendarWidth = $calendar.outerWidth() + paddingsWidth; + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + assert.strictEqual($overlayContent.width(), calendarWidth, 'popup width is correct'); + }); + + QUnit.test('should be equal to the calendar width + margins when dropDownOptions.width in not defined after editor width runtime change', function(assert) { + const dateBox = this.$dateBox.dxDateBox({ + opened: true, + pickerType: 'calendar' + }).dxDateBox('instance'); + + const $calendar = $(`.${CALENDAR_CLASS}`); + const paddingsWidth = parseInt($calendar.css('margin-left')) * 2; + const calendarWidth = $calendar.outerWidth() + paddingsWidth; + + dateBox.option('width', 153); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + assert.strictEqual($overlayContent.width(), calendarWidth, 'popup width is correct'); + }); + + QUnit.test('should be equal to dropDownOptions.width if it\'s defined', function(assert) { + this.$dateBox.dxDateBox({ + pickerType: 'calendar', + dropDownOptions: { + width: 500 + }, + opened: true + }); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + assert.strictEqual($overlayContent.outerWidth(), 500, 'overlay content width is correct'); + }); + + QUnit.test('should be equal to dropDownOptions.width even after editor input width change', function(assert) { + const dateBox = this.$dateBox.dxDateBox({ + pickerType: 'calendar', + dropDownOptions: { + width: 500 + }, + opened: true + }).dxDateBox('instance'); + + dateBox.option('width', 300); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + assert.strictEqual($overlayContent.outerWidth(), 500, 'overlay content width is correct'); + }); + + QUnit.test('should be equal to calendar width + margins if dropDownOptions.width is set to auto', function(assert) { + this.$dateBox.dxDateBox({ + pickerType: 'calendar', + dropDownOptions: { + width: 'auto' + }, + opened: true + }); + + const $calendar = $(`.${CALENDAR_CLASS}`); + const paddingsWidth = parseInt($calendar.css('margin-left')) * 2; + const calendarWidth = $calendar.outerWidth() + paddingsWidth; + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + assert.strictEqual($overlayContent.width(), calendarWidth, 'overlay content width is correct'); + }); + + QUnit.test('should be equal to wrapper width if dropDownOptions.width is set to 100%', function(assert) { + this.$dateBox.dxDateBox({ + pickerType: 'calendar', + dropDownOptions: { + width: '100%' + }, + opened: true + }); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + const $overlayWrapper = $(`.${OVERLAY_WRAPPER_CLASS}`); + assert.strictEqual($overlayContent.outerWidth(), $overlayWrapper.outerWidth(), 'overlay content width is correct'); + }); + + QUnit.test('should be calculated relative to wrapper when dropDownOptions.width is percent', function(assert) { + this.$dateBox.dxDateBox({ + pickerType: 'calendar', + dropDownOptions: { + width: '50%' + }, + opened: true + }); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + const $overlayWrapper = $(`.${OVERLAY_WRAPPER_CLASS}`); + assert.roughEqual($overlayContent.outerWidth(), $overlayWrapper.outerWidth() / 2, 0.1, 'overlay content width is correct'); + }); + + QUnit.test('should be calculated relative to wrapper after editor width runtime change', function(assert) { + const dateBox = this.$dateBox.dxDateBox({ + pickerType: 'calendar', + width: 600, + dropDownOptions: { + width: '50%' + }, + opened: true + }).dxDateBox('instance'); + + dateBox.option('width', 700); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + const $overlayWrapper = $(`.${OVERLAY_WRAPPER_CLASS}`); + assert.roughEqual($overlayContent.outerWidth(), $overlayWrapper.outerWidth() / 2, 0.1, 'overlay content width is correct'); + }); + + QUnit.test('should be equal to calendar width + margins even when dropDownOptions.container is defined', function(assert) { + this.$dateBox.dxDateBox({ + pickerType: 'calendar', + dropDownOptions: { + container: $('#containerWithWidth') + }, + opened: true + }); + + const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); + const $calendar = $(`.${CALENDAR_CLASS}`); + const paddingsWidth = parseInt($calendar.css('margin-left')) * 2; + const calendarWidth = $calendar.outerWidth() + paddingsWidth; + + assert.strictEqual($overlayContent.width(), calendarWidth, 'width is correct'); + }); + }); + + QUnit.test('dropDownOptions.width should be passed to popup', function(assert) { + this.$dateBox.dxDateBox({ + pickerType: 'calendar', + dropDownOptions: { + width: 500 + }, + opened: true + }); + + const popup = this.$dateBox.find(`.${POPUP_CLASS}`).dxPopup('instance'); + assert.strictEqual(popup.option('width'), 500, 'popup width option value is correct'); + }); + + QUnit.test('popup should have width equal to dropDownOptions.width even after editor input width change (T897820)', function(assert) { + const dateBox = this.$dateBox.dxDateBox({ + pickerType: 'calendar', + dropDownOptions: { + width: 500 + }, + opened: true + }).dxDateBox('instance'); + + dateBox.option('width', 300); + + const popup = this.$dateBox.find(`.${POPUP_CLASS}`).dxPopup('instance'); + assert.strictEqual(popup.option('width'), 500, 'popup width option value is correct'); + }); +}); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/datebox.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/datebox.tests.js index cefc32121d1d..f0c737c1a450 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/datebox.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/datebox.tests.js @@ -1,8 +1,6 @@ import '../../helpers/noIntl.js'; import $ from 'jquery'; -import Box from 'ui/box'; import Calendar from 'ui/calendar'; -import DateBox from 'ui/date_box'; import config from 'core/config'; import dateLocalization from 'common/core/localization/date'; import dateSerialization from 'core/utils/date_serialization'; @@ -10,7 +8,6 @@ import dateUtils from 'core/utils/date'; import devices from '__internal/core/m_devices'; import fx from 'common/core/animation/fx'; import keyboardMock from '../../helpers/keyboardMock.js'; -import { getActiveElement } from '../../helpers/shadowDom.js'; import messageLocalization from 'common/core/localization/message'; import localization from 'localization'; import ja from 'localization/messages/ja.json!'; @@ -18,16 +15,13 @@ import pointerMock from '../../helpers/pointerMock.js'; import support from '__internal/core/utils/m_support'; import typeUtils from 'core/utils/type'; import uiDateUtils from '__internal/ui/date_box/m_date_utils'; -import { noop } from 'core/utils/common'; -import { logger } from 'core/utils/console'; import { normalizeKeyName } from 'common/core/events/utils/index'; -import browser from 'core/utils/browser'; import '../../helpers/calendarFixtures.js'; +import 'ui/date_box'; import 'ui/validator'; import 'generic_light.css!'; -import { implementationsMap } from 'core/utils/size'; QUnit.testStart(() => { const markup = @@ -42,19 +36,10 @@ QUnit.testStart(() => { $('#widthRootStyle').css('width', '300px'); }); -const currentDate = new Date(2015, 11, 31); -const firstDayOfWeek = 0; -const BOX_CLASS = 'dx-box'; -const CALENDAR_CLASS = 'dx-calendar'; -const TIMEVIEW_CLASS = 'dx-timeview'; -const TIMEVIEW_CLOCK_CLASS = 'dx-timeview-clock'; const TEXTEDITOR_INPUT_CLASS = 'dx-texteditor-input'; const DATEBOX_CLASS = 'dx-datebox'; const DATEBOX_WRAPPER_CLASS = 'dx-datebox-wrapper'; -const DATEBOX_LIST_POPUP_SELECTOR = '.dx-datebox-wrapper-list .dx-popup-content'; const LIST_ITEM_SELECTOR = '.dx-list-item'; -const DATEBOX_ADAPTIVITY_MODE_CLASS = 'dx-datebox-adaptivity-mode'; -const LIST_ITEM_SELECTED_CLASS = 'dx-list-item-selected'; const STATE_FOCUSED_CLASS = 'dx-state-focused'; const BUTTONS_CONTAINER_CLASS = 'dx-texteditor-buttons-container'; const GESTURE_COVER_CLASS = 'dx-gesture-cover'; @@ -62,9 +47,6 @@ const DROP_DOWN_BUTTON_CLASS = 'dx-dropdowneditor-button'; const BUTTON_CLASS = 'dx-button'; const TODAY_BUTTON_CLASS = 'dx-button-today'; const DROP_DOWN_BUTTON_VISIBLE_CLASS = 'dx-dropdowneditor-button-visible'; -const OVERLAY_CONTENT_CLASS = 'dx-overlay-content'; -const OVERLAY_WRAPPER_CLASS = 'dx-overlay-wrapper'; -const POPUP_CLASS = 'dx-popup'; const LIST_CLASS = 'dx-list'; const CLEAR_BUTTON_AREA_CLASS = 'dx-clear-button-area'; const CALENDAR_CELL_CLASS = 'dx-calendar-cell'; @@ -72,9 +54,7 @@ const CALENDAR_TODAY_BUTTON_CLASS = 'dx-calendar-today-button'; const CALENDAR_CAPTION_BUTTON_CLASS = 'dx-calendar-caption-button'; const CALENDAR_NAVIGATOR_PREVIOUS_VIEW_CLASS = 'dx-calendar-navigator-previous-view'; const DROPDOWNEDITOR_OVERLAY_CLASS = 'dx-dropdowneditor-overlay'; -const NUMBERBOX_CLASS = 'dx-numberbox'; const NUMBERBOX_SPIN_DOWN_CLASS = 'dx-numberbox-spin-down'; -const SELECTBOX_CLASS = 'dx-selectbox'; const SHOW_INVALID_BADGE_CLASS = 'dx-show-invalid-badge'; const APPLY_BUTTON_SELECTOR = '.dx-popup-done.dx-button'; @@ -128,8 +108,6 @@ const getExpectedResult = (date, mode, stringDate) => { const prepareDateString = (type, year, month, day) => type === 'text' ? `${month}/${day}/${year}` : `${year}-${month}-${day}`; -const isAndroid = () => devices.real().android; - QUnit.module('datebox tests', moduleConfig, () => { QUnit.test('value is null after reset', function(assert) { const date = new Date(2012, 10, 26, 16, 40, 23); @@ -244,9 +222,8 @@ QUnit.module('datebox tests', moduleConfig, () => { }); const $dropDownButton = $dateBox.find(`.${DROP_DOWN_BUTTON_CLASS}`); - const expectedButtonsNumber = devices.real().deviceType === 'desktop' ? 0 : 1; - assert.equal($dropDownButton.length, expectedButtonsNumber, 'correct readOnly value'); + assert.equal($dropDownButton.length, 0, 'correct readOnly value'); }); QUnit.test('Datebox should set min/max attributes to datetime input in localized datetime format (T1252602)', function(assert) { @@ -692,10 +669,8 @@ QUnit.module('hidden input', {}, () => { assert.equal($hiddenInput.val(), expectedStringValue, 'input value is correct after widget value change'); }); - QUnit.test(`click on drop-down button should ${isAndroid() ? '' : 'not'} call click on input to show native picker, ${devices.real().platform} device (T824701, T950897)`, function(assert) { + QUnit.test('click on drop-down button should not call click on input to show native picker (T824701, T950897)', function(assert) { const clickStub = sinon.stub(); - const isAndroidDevice = isAndroid(); - const expectedCallCount = isAndroidDevice ? 1 : 0; const $element = $('#dateBox').dxDateBox({ pickerType: 'native', showDropDownButton: true @@ -709,7 +684,7 @@ QUnit.module('hidden input', {}, () => { .find(`.${DROP_DOWN_BUTTON_CLASS}`) .trigger('dxclick'); - assert.strictEqual(clickStub.callCount, expectedCallCount, `${devices.real().platform} device, editor should ${isAndroidDevice ? '' : 'not'} trigger click on the input`); + assert.strictEqual(clickStub.callCount, 0, 'editor should not trigger click on the input'); }); }); @@ -1194,15 +1169,6 @@ QUnit.module('dateView integration', { assert.equal(this.popup().$content().find('.dx-dateview').length, 1); }); - QUnit.test('dateView popup width should be equal to 100% on mobile ios', function(assert) { - if(devices.real().deviceType !== 'phone' || devices.real().platform !== 'ios') { - assert.ok(true, 'is actual only for mobile ios'); - return; - } - - assert.strictEqual(this.popup().option('width'), '100%', 'popup width is equal to 100%'); - }); - QUnit.test('readOnly input prop should be always true to prevent keyboard open if simulated dateView is using', function(assert) { this.instance.option('readOnly', false); assert.ok(this.$element.find('.' + TEXTEDITOR_INPUT_CLASS).prop('readOnly'), 'readonly prop specified correctly'); @@ -1369,27 +1335,6 @@ QUnit.module('dateView integration', { assert.equal($element.dxDateBox('instance')._strategy.NAME, 'Native', 'correct strategy is chosen'); }); - QUnit.test('pickerType should be \'native\' on android >= 4.4 (Q588373, Q588012)', function(assert) { - support.inputType = () => { - return true; - }; - - const originalDevice = devices.real(); - const currentDevice = devices.current(); - - try { - devices.real({ platform: 'android', deviceType: 'phone', version: [4, 4, 2], android: true }); - devices.current({ platform: 'android' }); - - const dateBox = $('#dateBoxWithPicker').dxDateBox().dxDateBox('instance'); - assert.strictEqual(dateBox.option('pickerType'), 'native'); - } finally { - support.inputType = this.originalInputType; - devices.real(originalDevice); - devices.current(currentDevice); - } - }); - QUnit.test('B230631 - Can not clear datebox field', function(assert) { this.instance.option({ value: new Date(), @@ -1625,3439 +1570,391 @@ QUnit.module('widget sizing render', {}, () => { }); }); -QUnit.module('datebox and calendar integration', () => { - QUnit.test('default', function(assert) { - const $element = $('#dateBox').dxDateBox({ pickerType: 'calendar' }); - - assert.ok($element.outerWidth() > 0, 'outer width of the element must be more than zero'); - }); - - QUnit.test('change width', function(assert) { - const $element = $('#dateBox').dxDateBox({ pickerType: 'calendar' }); - const instance = $element.dxDateBox('instance'); - const customWidth = 258; - - instance.option('width', customWidth); - - assert.strictEqual($element.outerWidth(), customWidth, 'outer width of the element must be equal to custom width'); - }); - - QUnit.test('change input value should change calendar value', function(assert) { - const $dateBox = $('#dateBox').dxDateBox({ - pickerType: 'calendar', - type: 'date', - value: new Date(2016, 1, 25) - }); - $($dateBox.find(`.${DROP_DOWN_BUTTON_CLASS}`)).trigger('dxclick'); - - const dateBox = $dateBox.dxDateBox('instance'); - const calendar = $('.dx-calendar').dxCalendar('instance'); - - const $input = $dateBox.find('.' + TEXTEDITOR_INPUT_CLASS); - let dateString = $input.val(); - dateString = dateString.slice(0, -1) + String(new Date().getYear() - 1).slice(-1); +QUnit.module('keyboard navigation', { + beforeEach: function() { + fx.off = true; - $input.val(''); - keyboardMock($input).type(dateString); - $($input).trigger('change'); + this.$dateBox = $('#dateBox'); - assert.deepEqual(calendar.option('value'), dateBox.option('value'), 'datebox value and calendar value are equal'); - assert.strictEqual(dateBox.option('isValid'), true, 'Editor should be marked as true'); - assert.strictEqual(dateBox.option('validationError'), null, 'No validation error should be specified for valid input'); - }); + this.dateBox = this.$dateBox + .dxDateBox({ + pickerType: 'calendar', + type: 'time', + focusStateEnabled: true, + min: new Date(2008, 7, 8, 4, 30), + value: new Date(2008, 7, 8, 5, 0), + max: new Date(2008, 7, 8, 6, 0) + }) + .dxDateBox('instance'); - QUnit.test('wrong value in input should mark datebox as invalid', function(assert) { - const $dateBox = $('#dateBox').dxDateBox({ - value: null, - type: 'date', - pickerType: 'calendar' - }); + this.$input = this.$dateBox.find(`.${TEXTEDITOR_INPUT_CLASS}`); + this.keyboard = keyboardMock(this.$input); + }, + afterEach: function() { + fx.off = false; + } +}, () => { + QUnit.testInActiveWindow('popup hides on tab', function(assert) { + this.$input.focusin(); - const dateBox = $dateBox.dxDateBox('instance'); - const $input = $dateBox.find('.' + TEXTEDITOR_INPUT_CLASS); + assert.ok(this.$dateBox.hasClass(STATE_FOCUSED_CLASS), 'element is focused'); + this.dateBox.option('opened', true); + this.keyboard.keyDown('tab'); + assert.ok(this.$dateBox.hasClass(STATE_FOCUSED_CLASS), 'element is focused'); - keyboardMock($input).type('blabla'); - $($input).trigger('change'); - assert.equal($input.val(), 'blabla', 'input value should not be erased'); - assert.strictEqual(dateBox.option('value'), null, 'Editor\'s value should be reset'); - assert.strictEqual(dateBox.option('isValid'), false, 'Editor should be marked as invalid'); - const validationError = dateBox.option('validationError'); - assert.ok(validationError, 'Validation error should be specified'); - assert.ok(validationError.editorSpecific, 'editorSpecific flag should be added'); + assert.equal(this.dateBox.option('opened'), false, 'popup is hidden'); }); - QUnit.test('datebox should not be revalidated when readOnly option changed', function(assert) { - const dateBox = $('#dateBox').dxDateBox({ - readOnly: false - }).dxValidator({ - validationRules: [{ - type: 'required', - message: 'Date of birth is required' - }] - }).dxDateBox('instance'); - - dateBox.option('readOnly', true); - dateBox.option('readOnly', false); + QUnit.testInActiveWindow('home/end should not be handled', function(assert) { + this.$input.focusin(); + this.dateBox.option('opened', true); + const $timeList = $(`.${LIST_CLASS}`); - assert.ok(dateBox.option('isValid'), 'dateBox is valid'); - assert.notOk($('#dateBox').hasClass('dx-invalid'), 'dateBox is not marked as invalid'); + this.keyboard.keyDown('down'); + this.keyboard.keyDown('end'); + assert.ok(!$timeList.find(LIST_ITEM_SELECTOR).eq(0).hasClass(STATE_FOCUSED_CLASS), 'element is not focused'); + this.keyboard.keyDown('home'); + assert.ok(!$timeList.find(LIST_ITEM_SELECTOR).eq(0).hasClass(STATE_FOCUSED_CLASS), 'element is not focused'); }); - QUnit.test('wrong value in input should mark time datebox as invalid', function(assert) { - const $dateBox = $('#dateBox').dxDateBox({ - value: null, - type: 'time', - pickerType: 'calendar' - }); - - const dateBox = $dateBox.dxDateBox('instance'); - const $input = $dateBox.find('.' + TEXTEDITOR_INPUT_CLASS); - - keyboardMock($input).type('blabla'); - $($input).trigger('change'); + QUnit.testInActiveWindow('arrow keys control', function(assert) { + this.$input.focusin(); + this.dateBox.option('opened', true); + this.keyboard.keyDown('down'); - assert.equal($input.val(), 'blabla', 'input value should not be erased'); - assert.strictEqual(dateBox.option('value'), null, 'Editor\'s value should be reset'); - assert.strictEqual(dateBox.option('isValid'), false, 'Editor should be marked as invalid'); - const validationError = dateBox.option('validationError'); - assert.ok(validationError, 'Validation error should be specified'); - assert.ok(validationError.editorSpecific, 'editorSpecific flag should be added'); - }); + const $timeList = $(`.${LIST_CLASS}`); - QUnit.test('wrong value in input should mark pre-filled datebox as invalid', function(assert) { - const value = new Date(2013, 2, 2); + assert.ok($timeList.find(LIST_ITEM_SELECTOR).eq(2).hasClass(STATE_FOCUSED_CLASS), 'correct item is focused'); - const $dateBox = $('#dateBox').dxDateBox({ - type: 'date', - value: new Date(value), - pickerType: 'calendar' - }); + this.keyboard.keyDown('down'); + assert.ok($timeList.find(LIST_ITEM_SELECTOR).eq(3).hasClass(STATE_FOCUSED_CLASS), 'correct item is focused'); - const dateBox = $dateBox.dxDateBox('instance'); - const $input = $dateBox.find('.' + TEXTEDITOR_INPUT_CLASS); + this.keyboard.keyDown('down'); + assert.ok($timeList.find(LIST_ITEM_SELECTOR).eq(0).hasClass(STATE_FOCUSED_CLASS), 'correct item is focused'); - $input.val(''); - keyboardMock($input).type('blabla'); - $($input).trigger('change'); + this.keyboard.keyDown('up'); + assert.ok($timeList.find(LIST_ITEM_SELECTOR).eq(3).hasClass(STATE_FOCUSED_CLASS), 'correct item is focused'); - assert.equal($input.val(), 'blabla', 'input value should not be erased'); - assert.deepEqual(dateBox.option('value'), value, 'Editor\'s value should not be changed'); - assert.strictEqual(dateBox.option('isValid'), false, 'Editor should be marked as invalid'); + this.keyboard.keyDown('enter'); + assert.strictEqual(this.dateBox.option('opened'), false, 'popup is hidden'); - const validationError = dateBox.option('validationError'); - assert.ok(validationError, 'Validation error should be specified'); - assert.ok(validationError.editorSpecific, 'editorSpecific flag should be added'); + const selectedDate = this.dateBox.option('value'); + assert.strictEqual(selectedDate.getHours(), 6, 'hours is right'); + assert.strictEqual(selectedDate.getMinutes(), 0, 'minutes is right'); }); - QUnit.test('correct value in input should mark datebox as valid but keep text', function(assert) { - const $dateBox = $('#dateBox').dxDateBox({ - value: null, - type: 'date', - pickerType: 'calendar' - }); + QUnit.test('apply contoured date on enter for date and datetime mode', function(assert) { + this.dateBox = this.$dateBox + .dxDateBox({ + pickerType: 'calendar', + type: 'date', + applyValueMode: 'useButtons', + focusStateEnabled: true, + min: new Date(2008, 6, 8, 4, 30), + value: new Date(2008, 7, 8, 5, 0), + max: new Date(2008, 9, 8, 6, 0), + opened: true + }) + .dxDateBox('instance'); - const dateBox = $dateBox.dxDateBox('instance'); - const $input = $dateBox.find('.' + TEXTEDITOR_INPUT_CLASS); - const keyboard = keyboardMock($input); + const $input = this.$dateBox.find(`.${TEXTEDITOR_INPUT_CLASS}`); - keyboard - .type('blabla') - .change(); + $($input).trigger($.Event('keydown', { key: 'ArrowUp' })); + $($input).trigger($.Event('keydown', { key: 'ArrowDown' })); + $($input).trigger($.Event('keydown', { key: 'ArrowUp' })); + $($input).trigger($.Event('keydown', { key: 'Enter' })); - $input.val(''); - keyboard - .type('3/2/2014') - .change(); + assert.equal(this.dateBox.option('opened'), false, 'popup is hidden'); - assert.equal($input.val(), '3/2/2014', 'input value should not be erased'); - assert.deepEqual(dateBox.option('value'), new Date(2014, 2, 2), 'Editor\'s value should be set'); - assert.strictEqual(dateBox.option('isValid'), true, 'Editor should be marked as valid'); - assert.strictEqual(dateBox.option('validationError'), null, 'No validation error should be specified for valid input'); + const selectedDate = this.dateBox.option('value'); + assert.equal(selectedDate.getDate(), 1, 'day is right'); }); - QUnit.test('calendar picker should be used on generic device by default and \'type\' is \'date\'', function(assert) { - const currentDevice = devices.current(); - const realDevice = devices.real(); - - devices.real({ platform: 'generic', deviceType: 'desktop', phone: false }); - devices.current({ deviceType: 'desktop' }); + QUnit.test('Enter key press prevents default when popup in opened', function(assert) { + assert.expect(1); - try { - const $dateBox = $('#dateBox').dxDateBox(); - const instance = $dateBox.dxDateBox('instance'); + let prevented = 0; - assert.equal(instance.option('pickerType'), 'calendar'); - assert.equal(instance._strategy.NAME, 'Calendar'); - } finally { - devices.current(currentDevice); - devices.real(realDevice); - } - }); + const $dateBox = $('
').appendTo('body').dxDateBox({ + pickerType: 'calendar', + focusStateEnabled: true, + value: new Date(2015, 5, 13), + opened: true + }); - QUnit.test('calendar picker should not be used on generic device by default and \'type\' is not \'date\'', function(assert) { - const currentDevice = devices.current(); - devices.current({ platform: 'generic', deviceType: 'desktop' }); + const $input = $dateBox.find(`.${TEXTEDITOR_INPUT_CLASS}`); + const keyboard = keyboardMock($input); try { - const $dateBox = $('#dateBox').dxDateBox({ - pickerType: 'calendar', - type: 'time' + $($dateBox).on('keydown', e => { + if(e.isDefaultPrevented()) { + prevented++; + } }); - assert.ok(!$dateBox.hasClass(DATEBOX_CLASS + '-calendar')); - } finally { - devices.current(currentDevice); - } - }); - QUnit.test('calendar picker should not be used on mobile device by default', function(assert) { - const realDevice = devices.real(); - devices.real({ platform: 'android' }); + keyboard.keyDown('enter'); + assert.equal(prevented, 1, 'defaults prevented on enter key press'); - try { - const $dateBox = $('#dateBox').dxDateBox(); - assert.ok(!$dateBox.hasClass(DATEBOX_CLASS + '-calendar')); } finally { - devices.real(realDevice); + $dateBox.remove(); } }); - QUnit.test('correct default value for \'minZoomLevel\' option', function(assert) { - const instance = $('#dateBox').dxDateBox({ - type: 'date', + QUnit.testInActiveWindow('the \'shift+tab\' key press leads to the cancel button focus if the input is focused', function(assert) { + this.dateBox.option({ pickerType: 'calendar', - opened: true - }).dxDateBox('instance'); + type: 'datetime', + opened: true, + applyValueMode: 'useButtons' + }); + + const $input = this.$dateBox.find(`.${TEXTEDITOR_INPUT_CLASS}`); - const calendar = getInstanceWidget(instance); + $input + .focus() + .trigger($.Event('keydown', { + key: 'Tab', + shiftKey: true + })); - assert.equal(calendar.option('minZoomLevel'), 'century', '\'minZoomLevel\' option value is correct'); + const $cancelButton = this.dateBox._popup.$wrapper().find('.dx-button.dx-popup-cancel'); + assert.ok($cancelButton.hasClass('dx-state-focused'), 'cancel button is focused'); }); - QUnit.test('correct default value for \'maxZoomLevel\' option', function(assert) { - const instance = $('#dateBox').dxDateBox({ - type: 'date', + QUnit.test('pressing tab should set focus on calendar prev button in popup', function(assert) { + this.dateBox.option({ pickerType: 'calendar', - opened: true - }).dxDateBox('instance'); + type: 'date', + applyValueMode: 'useButtons', + opened: true, + min: null, + max: null, + }); - const calendar = getInstanceWidget(instance); + const $input = this.$dateBox.find(`.${TEXTEDITOR_INPUT_CLASS}`); - assert.equal(calendar.option('maxZoomLevel'), 'month', '\'maxZoomLevel\' option value is correct'); - }); + $input + .focus() + .trigger($.Event('keydown', { + key: 'Tab', + })); - QUnit.test('DateBox \'minZoomLevel\' option should affect on Calendar \'minZoomLevel\' option', function(assert) { - const instance = $('#dateBox').dxDateBox({ - type: 'date', - pickerType: 'calendar', - calendarOptions: { minZoomLevel: 'year' }, - opened: true - }).dxDateBox('instance'); + const $prevButton = this.dateBox._popup.$wrapper().find(`.${CALENDAR_NAVIGATOR_PREVIOUS_VIEW_CLASS}`); - let calendar = getInstanceWidget(instance); + assert.ok($prevButton.hasClass(STATE_FOCUSED_CLASS)); + }); - assert.equal(calendar.option('minZoomLevel'), 'year', 'calendar \'minZoomLevel\' option is correct on init'); + QUnit.test('pressing tab should set focus on first item in popup with custom items', function(assert) { + this.dateBox.option({ + pickerType: 'calendar', + type: 'date', + applyValueMode: 'useButtons', + opened: true, + dropDownOptions: { + toolbarItems: [{ + widget: 'dxButton', + toolbar: 'top', + location: 'before', + options: { + text: 'Button', + }, + }, + { + widget: 'dxTextBox', + toolbar: 'bottom', + location: 'before', + options: { + text: 'Text box', + }, + }], + }, + }); + const $input = this.$dateBox.find(`.${TEXTEDITOR_INPUT_CLASS}`); - instance.close(); - instance.option('calendarOptions.minZoomLevel', 'month'); - instance.open(); - calendar = getInstanceWidget(instance); + $input + .focus() + .trigger($.Event('keydown', { + key: 'Tab', + })); - assert.equal(calendar.option('minZoomLevel'), 'month', 'calendar \'minZoomLevel\' option after dateBox option change'); + const $firstItem = this.dateBox._popup.$wrapper().find(BUTTON_SELECTOR); + assert.ok($firstItem.hasClass(STATE_FOCUSED_CLASS)); }); - QUnit.test('DateBox \'maxZoomLevel\' option should affect on Calendar \'maxZoomLevel\' option', function(assert) { - const instance = $('#dateBox').dxDateBox({ - type: 'date', + QUnit.test('pressing tab + shift should set focus on last item in popup with custom items', function(assert) { + this.dateBox.option({ pickerType: 'calendar', - calendarOptions: { maxZoomLevel: 'century' }, - opened: true - }).dxDateBox('instance'); + type: 'date', + applyValueMode: 'useButtons', + opened: true, + dropDownOptions: { + toolbarItems: [{ + widget: 'dxButton', + toolbar: 'top', + location: 'before', + options: { + text: 'Button', + }, + }, + { + widget: 'dxTextBox', + toolbar: 'bottom', + location: 'before', + options: { + text: 'Text box', + }, + }], + }, + }); + const $input = this.$dateBox.find(`.${TEXTEDITOR_INPUT_CLASS}`); - let calendar = getInstanceWidget(instance); + $input + .focus() + .trigger($.Event('keydown', { + key: 'Tab', + shiftKey: true + })); - assert.equal(calendar.option('maxZoomLevel'), 'century', 'calendar \'maxZoomLevel\' option is correct on init'); + const $lastItem = this.dateBox._popup.$wrapper().find(TEXTBOX_SELECTOR); + assert.ok($lastItem.hasClass(STATE_FOCUSED_CLASS)); + }); - instance.close(); - instance.option('calendarOptions.maxZoomLevel', 'year'); - instance.open(); - calendar = getInstanceWidget(instance); + QUnit.test('Home and end key press prevent default when popup in opened (T587313)', function(assert) { + assert.expect(1); - assert.equal(calendar.option('maxZoomLevel'), 'year', 'calendar \'maxZoomLevel\' option after dateBox option change'); - }); + let prevented = 0; - QUnit.test('T208534 - calendar value should depend on datebox text option', function(assert) { - const instance = $('#dateBox').dxDateBox({ - type: 'date', - pickerType: 'calendar', - value: new Date(2015, 4, 12), - valueChangeEvent: 'keyup' - }).dxDateBox('instance'); + this.dateBox.option('opened', true); - const kb = keyboardMock(instance._input()); + this.$dateBox.on('keydown', (e) => { + if(e.isDefaultPrevented()) { + prevented++; + } + }); - kb - .press('end') - .press('backspace') - .type('4'); + this.keyboard.keyDown('home'); + this.keyboard.keyDown('end'); - instance.open(); - assert.deepEqual(new Date(2014, 4, 12), instance._strategy._widget.option('value'), 'calendar value is correct'); + assert.equal(prevented, 0, 'defaults prevented on home and end keys'); }); - QUnit.test('calendar value should depend on datebox text option when calendar is opened', function(assert) { - const instance = $('#dateBox').dxDateBox({ - type: 'date', - pickerType: 'calendar', - value: new Date(2015, 4, 12), - valueChangeEvent: 'keyup', - opened: true - }).dxDateBox('instance'); + QUnit.test('Home and end key press does not prevent default when popup in not opened (T587313)', function(assert) { + assert.expect(1); - const kb = keyboardMock(instance._input()); - const calendar = instance._strategy._widget; + let prevented = 0; - kb - .caret(9) - .press('backspace') - .type('4'); + this.dateBox.option('opened', false); - assert.deepEqual(new Date(2014, 4, 12), calendar.option('value'), 'calendar value is correct'); + this.$dateBox.on('keydown', (e) => { + if(e.isDefaultPrevented()) { + prevented++; + } + }); - kb.press('backspace'); - assert.deepEqual(new Date(201, 4, 12), calendar.option('value'), 'calendar value is correct'); + this.keyboard.keyDown('home'); + this.keyboard.keyDown('end'); - kb.type('3'); - assert.deepEqual(new Date(2013, 4, 12), calendar.option('value'), 'calendar value is correct'); + assert.equal(prevented, 0, 'defaults has not prevented on home and end keys'); }); - QUnit.test('changing \'displayFormat\' should update input value', function(assert) { - const $dateBox = $('#dateBox').dxDateBox({ - value: new Date('03/10/2015'), - pickerType: 'calendar', - type: 'date' + QUnit.testInActiveWindow('Unsupported key handlers must be processed correctly', function(assert) { + this.dateBox.option({ + pickerType: 'list', + type: 'time' }); - const dateBox = $dateBox.dxDateBox('instance'); - dateBox.option('displayFormat', 'shortDateShortTime'); - assert.equal($dateBox.find('.' + TEXTEDITOR_INPUT_CLASS).val(), '3/10/2015, 12:00 AM', 'input value is updated'); - }); + const $input = this.$dateBox.find(`.${TEXTEDITOR_INPUT_CLASS}`); + const keyboard = keyboardMock($input); - QUnit.test('displayFormat should affect on timeView', function(assert) { - const $dateBox = $('#dateBox').dxDateBox({ - value: new Date('03/10/2015'), - displayFormat: 'shortdateshorttime', - pickerType: 'calendar', - opened: true, - type: 'datetime' - }); + this.dateBox.focus(); - const dateBox = $dateBox.dxDateBox('instance'); - const $content = $(dateBox._popup.$content()); - const timeView = $content.find('.' + TIMEVIEW_CLASS).dxTimeView('instance'); + let isNoError = true; - assert.notOk(timeView.option('use24HourFormat'), 'using 12 hour format'); + try { + keyboard + .press('down') + .press('up') + .press('right') + .press('left'); + } catch(e) { + isNoError = false; + } - dateBox.option('displayFormat', 'hour'); - assert.ok(timeView.option('use24HourFormat'), 'using 24 hour format'); + assert.ok(isNoError, 'key handlers processed without errors'); }); - QUnit.test('disabledDates correctly displays', function(assert) { - const instance = $('#dateBox').dxDateBox({ + QUnit.test('Pressing escape when focus \'today\' button must hide the popup', function(assert) { + const escapeKeyDown = $.Event('keydown', { key: 'Escape' }); + this.dateBox.option({ type: 'date', pickerType: 'calendar', - value: new Date(2015, 4, 12), - disabledDates: [new Date(2015, 4, 13)], - opened: true - }).dxDateBox('instance'); + applyValueMode: 'useButtons' + }); + this.dateBox.open(); - const calendar = getInstanceWidget(instance); - const $disabledCell = calendar.$element().find('.dx-calendar-empty-cell'); + $(this.dateBox.content()) + .parent() + .find('.dx-button-today') + .trigger(escapeKeyDown); - assert.equal($disabledCell.length, 1, 'There is one disabled cell'); - assert.equal($disabledCell.text(), '13', 'Correct cell is disabled'); + assert.ok(!this.dateBox.option('opened')); }); - QUnit.test('disabledDates should not be called for the dates out of range[min, max]', function(assert) { - let callCount = 0; - - $('#dateBox').dxDateBox({ - type: 'date', - pickerType: 'calendar', - value: new Date(2019, 11, 10), - min: new Date(2019, 11, 15), - max: new Date(2019, 11, 20), - disabledDates: () => { - ++callCount; - return true; - }, - opened: true - }).dxDateBox('instance'); - - assert.equal(callCount, 12, 'disabledDates has been called 6 times on init, 6 times on [min; max] for focusing'); - }); - - QUnit.test('disabledDates correctly displays after optionChanged', function(assert) { - const instance = $('#dateBox').dxDateBox({ - type: 'date', - pickerType: 'calendar', - value: new Date(2015, 4, 12), - disabledDates: [new Date(2015, 4, 13)], - opened: true - }).dxDateBox('instance'); - - instance.option('disabledDates', e => { - if(e.date.getDate() === 14 && e.date.getMonth() === 3) { - return true; - } - }); - - const calendar = getInstanceWidget(instance); - const $disabledCell = calendar.$element().find('.dx-calendar-empty-cell'); - - assert.equal($disabledCell.length, 1, 'There is one disabled cell'); - assert.equal($disabledCell.text(), '14', 'Correct cell is disabled'); - }); - - QUnit.test('disabledDates argument contains correct component parameter', function(assert) { - const stub = sinon.stub(); - - $('#dateBox').dxDateBox({ - type: 'date', - pickerType: 'calendar', - value: new Date(2015, 4, 12), - disabledDates: stub, - opened: true - }); - - const component = stub.lastCall.args[0].component; - assert.equal(component.NAME, 'dxDateBox', 'Correct component'); - }); - - QUnit.test('datebox with the \'datetime\' type should keep event subscriptions', function(assert) { - const stub = sinon.stub(); - - const dateBox = $('#dateBox').dxDateBox({ - type: 'datetime', - pickerType: 'calendar', - value: new Date(2015, 4, 12), - adaptivityEnabled: true, - onInitialized(e) { - e.component.on('optionChanged', stub); - } - }).dxDateBox('instance'); - - assert.equal(stub.callCount, 1, 'set text on render'); - - dateBox.option('opened', true); - - assert.equal(stub.callCount, 2, '\'opened\' optionChanged event has been raised'); - }); - - QUnit.test('Today button should be hidden if calendar is hidden', function(assert) { - const $element = $('#dateBox').dxDateBox({ - pickerType: 'calendar', - type: 'datetime', - calendarOptions: { - visible: false - }, - opened: true - }); - const instance = $element.dxDateBox('instance'); - const $todayButton = $(instance.content()).parent().find('.dx-button-today'); - - assert.strictEqual($todayButton.length, 0); - }); - - - QUnit.test('Today button should be hidden if calendar visibility is changed', function(assert) { - const $element = $('#dateBox').dxDateBox({ - pickerType: 'calendar', - type: 'datetime', - opened: true - }); - const instance = $element.dxDateBox('instance'); - - instance.option('calendarOptions.visible', false); - assert.strictEqual($(instance.content()).parent().find('.dx-button-today').length, 0); - - instance.option('calendarOptions.visible', true); - assert.strictEqual($(instance.content()).parent().find('.dx-button-today').length, 1); - }); - - QUnit.test('change year via scroll should log proper year in on value change event (T1229926)', function(assert) { - const valueChangedHandle = sinon.spy(); - const date = new Date(); - const currentYear = date.getFullYear(); - const datebox = $('#dateBox').dxDateBox({ - type: 'date', - value: date, - displayFormat: 'M/dd/yyyy', - valueChangeEvent: 'dxmousewheel', - useMaskBehavior: true, - onValueChanged: valueChangedHandle - }).dxDateBox('instance'); - - const $input = $(datebox.element()).find(`.${TEXTEDITOR_INPUT_CLASS}`); - const pointer = pointerMock($input); - const keyboard = keyboardMock($input, true); - - keyboard.caret({ start: 12, end: 15 }); - - $input.trigger('dxclick'); - - pointer.wheel(1); - - let changedValue = valueChangedHandle.getCall(0).args[0]; - assert.strictEqual(valueChangedHandle.callCount, 1, 'handler has been called once'); - assert.deepEqual(new Date(changedValue.value).getFullYear(), currentYear + 1, 'value year is correct'); assert.deepEqual(new Date(changedValue.previousValue).getFullYear(), currentYear, 'previous value year is correct'); - - pointer.wheel(1); - - changedValue = valueChangedHandle.getCall(1).args[0]; - assert.strictEqual(valueChangedHandle.callCount, 2, 'handler has been called twice'); - assert.deepEqual(new Date(changedValue.value).getFullYear(), currentYear + 2, 'value year is correct'); - assert.deepEqual(new Date(changedValue.previousValue).getFullYear(), currentYear + 1, 'previous value year is correct'); - }); -}); - -QUnit.module('datebox w/ calendar', { - beforeEach: function() { - this.clock = sinon.useFakeTimers(new Date().valueOf()); - fx.off = true; - - this.fixture = new DevExpress.ui.testing.DateBoxFixture('#dateBox', { - value: currentDate, - calendarOptions: { - currentDate, - firstDayOfWeek - }, - pickerType: 'calendar' - }); - this.reinitFixture = (options) => { - this.fixture.dispose(); - this.fixture = new DevExpress.ui.testing.DateBoxFixture('#dateBox', options); - }; - }, - afterEach: function() { - this.fixture.dispose(); - fx.off = false; - this.clock.restore(); - } -}, () => { - QUnit.test('DateBox is defined', function(assert) { - assert.ok(this.fixture.dateBox); - }); - - QUnit.test('DateBox can be instantiated', function(assert) { - assert.ok(this.fixture.dateBox instanceof DateBox); - }); - - QUnit.test('DateBox must render an input', function(assert) { - assert.ok(this.fixture.input.length); - }); - - QUnit.test('open must set \'opened\' option', function(assert) { - assert.ok(!this.fixture.dateBox.option('opened')); - this.fixture.dateBox.open(); - assert.ok(this.fixture.dateBox.option('opened')); - }); - - QUnit.test('calendarOptions must be passed to dxCalendar on initialization', function(assert) { - this.fixture.dateBox.open(); - currentDate.setDate(1); - assert.deepEqual(getInstanceWidget(this.fixture.dateBox).option('currentDate'), currentDate); - assert.deepEqual(getInstanceWidget(this.fixture.dateBox).option('firstDayOfWeek'), firstDayOfWeek); - }); - - QUnit.test('Clicking _calendarContainer must not close dropDown', function(assert) { - this.fixture.dateBox.open(); - pointerMock(this.fixture.dateBox._calendarContainer).click(); - assert.ok(this.fixture.dateBox.option('opened')); - }); - - QUnit.test('DateBox must update the input value when the value option changes', function(assert) { - const date = new Date(2011, 11, 11); - this.fixture.dateBox.option('value', date); - assert.deepEqual(this.fixture.input.val(), dateLocalization.format(date, this.fixture.format)); - }); - - QUnit.test('DateBox must immediately display \'value\' passed via the constructor on rendering', function(assert) { - const date = new Date(2010, 10, 10); - - this.reinitFixture({ - value: date, - calendarOptions: { currentDate, firstDayOfWeek }, - pickerType: 'calendar' - }); - - assert.deepEqual(this.fixture.input.val(), dateLocalization.format(date, this.fixture.format)); - }); - - QUnit.test('DateBox should pass empty string value to calendar if value is empty string', function(assert) { - this.reinitFixture({ - value: '', - pickerType: 'calendar', - opened: true - }); - - assert.equal(this.fixture.dateBox._strategy._widget.option('value'), '', 'value is equal to empty string'); - }); - - QUnit.test('DateBox must show the calendar with a proper date selected', function(assert) { - const date = new Date(2011, 11, 11); - this.fixture.dateBox.option('value', date); - this.fixture.dateBox.open(); - assert.deepEqual(getInstanceWidget(this.fixture.dateBox).option('value'), date); - }); - - QUnit.test('DateBox must update its value when a date is selected in the calendar when applyValueMode=\'instantly\'', function(assert) { - const date = new Date(2011, 11, 11); - - this.reinitFixture({ - applyValueMode: 'instantly', - pickerType: 'calendar' - }); - - this.fixture.dateBox.open(); - getInstanceWidget(this.fixture.dateBox).option('value', date); - // this.fixture.dateBox.close(); - assert.strictEqual(this.fixture.dateBox.option('value'), date); - }); - - QUnit.test('DateBox must update the calendar value when the CalendarPicker.option(\'value\') changes', function(assert) { - this.reinitFixture({ - applyValueMode: 'useButtons', - pickerType: 'calendar', - }); - - const date = new Date(2011, 11, 11); - this.fixture.dateBox.open(); - this.fixture.dateBox.option('value', date); - assert.deepEqual(getInstanceWidget(this.fixture.dateBox).option('value'), date); - }); - - QUnit.test('When typing a correct date, dateBox must not make a redundant _setInputValue call', function(assert) { - let _setInputValueCallCount = 0; - - const mockSetInputValue = () => { - ++_setInputValueCallCount; - }; - - this.fixture.dateBox._setInputValue = mockSetInputValue; - this.fixture.dateBox.open(); - this.fixture.typeIntoInput('11/11/2011', this.fixture.input); - assert.strictEqual(_setInputValueCallCount, 0); - }); - - QUnit.test('Swiping must not close the calendar', function(assert) { - $(this.fixture.dateBox._input()).focus(); - this.fixture.dateBox.open(); - pointerMock(this.fixture.dateBox._strategy._calendarContainer).start().swipeStart().swipeEnd(1); - assert.strictEqual(this.fixture.dateBox._input()[0], getActiveElement()); - }); - - QUnit.test('Pressing escape must hide the calendar and clean focus', function(assert) { - const escapeKeyDown = $.Event('keydown', { key: 'Escape' }); - this.fixture.dateBox.option('focusStateEnabled', true); - this.fixture.dateBox.open(); - $(this.fixture.dateBox._input()).trigger(escapeKeyDown); - assert.ok(!this.fixture.dateBox.option('opened')); - assert.ok(!this.fixture.dateBox._input().is(':focus')); - }); - - QUnit.test('dateBox must show the calendar with proper LTR-RTL mode', function(assert) { - this.fixture.dateBox.option('rtlEnabled', true); - this.fixture.dateBox.open(); - assert.ok(getInstanceWidget(this.fixture.dateBox).option('rtlEnabled')); - }); - - QUnit.test('dateBox should not reposition the calendar icon in RTL mode', function(assert) { - let iconRepositionCount = 0; - - const _repositionCalendarIconMock = () => { - ++iconRepositionCount; - }; - - this.fixture.dateBox._repositionCalendarIcon = _repositionCalendarIconMock; - this.fixture.dateBox.option('rtl', true); - assert.strictEqual(iconRepositionCount, 0); - }); - - QUnit.test('dateBox must apply the wrapper class with appropriate picker type to the drop-down overlay wrapper', function(assert) { - const dateBox = this.fixture.dateBox; - dateBox.open(); - assert.ok(this.fixture.dateBox._popup.$wrapper().hasClass(DATEBOX_WRAPPER_CLASS + '-' + dateBox.option('pickerType'))); - }); - - QUnit.test('dateBox must correctly reopen the calendar after refreshing when it was not hidden beforehand', function(assert) { - this.fixture.dateBox.open(); - this.fixture.dateBox._refresh(); - assert.ok(this.fixture.dateBox._$popup.dxPopup('instance').option('visible')); - }); - - QUnit.test('Changing the \'value\' option must invoke the \'onValueChanged\' action', function(assert) { - this.fixture.dateBox.option('onValueChanged', () => { - assert.ok(true); - }); - this.fixture.dateBox.option('value', new Date(2015, 6, 14)); - }); - - QUnit.test('dateBox\'s \'min\' and \'max\' options equal to undefined (T171537)', function(assert) { - assert.strictEqual(this.fixture.dateBox.option('min'), undefined); - assert.strictEqual(this.fixture.dateBox.option('max'), undefined); - }); - - QUnit.test('dateBox must pass min and max to the created calendar', function(assert) { - const min = new Date(2010, 9, 10); - const max = new Date(2010, 11, 10); - this.reinitFixture({ - min, - max, - pickerType: 'calendar' - }); - this.fixture.dateBox.open(); - assert.ok(dateUtils.dateInRange(getInstanceWidget(this.fixture.dateBox).option('currentDate'), min, max)); - }); - - QUnit.test('dateBox should not change value when setting to an earlier date than min; and setting to a later date than max', function(assert) { - const min = new Date(2010, 10, 5); - const max = new Date(2010, 10, 25); - const earlyDate = new Date(min.getFullYear(), min.getMonth(), min.getDate() - 1); - const lateDate = new Date(max.getFullYear(), max.getMonth(), max.getDate() + 1); - - this.reinitFixture({ - min, - max, - pickerType: 'calendar' - }); - - this.fixture.dateBox.option('value', earlyDate); - assert.deepEqual(this.fixture.dateBox.option('value'), earlyDate); - - this.fixture.dateBox.option('value', lateDate); - assert.deepEqual(this.fixture.dateBox.option('value'), lateDate); - }); - - QUnit.test('should execute custom validator while validation state reevaluating', function(assert) { - this.reinitFixture({ opened: true }); - - const dateBox = this.fixture.dateBox; - - dateBox.$element().dxValidator({ - validationRules: [{ - type: 'custom', - validationCallback: () => false - }] - }); - - const cell = dateBox._popup.$wrapper().find(`.${CALENDAR_CELL_CLASS}`); - - assert.ok(dateBox.option('isValid')); - assert.strictEqual(dateBox.option('text'), ''); - - $(cell).trigger('dxclick'); - - assert.notOk(dateBox.option('isValid')); - assert.notStrictEqual(dateBox.option('text'), ''); - }); - - QUnit.test('should rise validation event once after value is changed by calendar (T714599)', function(assert) { - const validationCallbackStub = sinon.stub().returns(false); - const dateBox = $('#dateBoxWithPicker') - .dxDateBox({ - type: 'datetime', - pickerType: 'calendar', - value: new Date(2015, 5, 9, 15, 54, 13), - opened: true - }) - .dxValidator({ - validationRules: [{ - type: 'custom', - validationCallback: validationCallbackStub - }] - }) - .dxDateBox('instance'); - - $(`.${CALENDAR_CELL_CLASS}`).eq(0).trigger('dxclick'); - $(APPLY_BUTTON_SELECTOR).trigger('dxclick'); - - assert.notOk(dateBox.option('opened')); - assert.ok(validationCallbackStub.calledOnce); - }); - - QUnit.test('Editor should reevaluate validation state after change text to the current value', function(assert) { - this.reinitFixture({ - min: new Date(2010, 10, 5), - value: new Date(2010, 10, 10), - type: 'date', - pickerType: 'calendar' - }); - - const dateBox = this.fixture.dateBox; - - $(dateBox._input()) - .val('11/3/2010') - .change(); - - assert.notOk(dateBox.option('isValid'), 'Editor isn\'t valid'); - assert.equal(dateBox.option('text'), '11/3/2010'); - - dateBox.open(); - - const $selectedDate = dateBox._popup.$wrapper().find('.dx-calendar-selected-date'); - $($selectedDate).trigger('dxclick'); - - assert.ok(dateBox.option('isValid'), 'Editor is valid'); - assert.equal(dateBox.option('text'), '11/10/2010'); - }); - - QUnit.test('In dateTime strategy buttons should be placed in popup bottom', function(assert) { - this.reinitFixture({ - type: 'datetime', - applyValueMode: 'useButtons', - pickerType: 'calendar' - }); - - this.fixture.dateBox.open(); - - assert.equal($('.dx-popup-bottom .dx-button').length, 3, 'two buttons is in popup bottom'); - }); - - QUnit.test('Click on apply button', function(assert) { - const onValueChangedHandler = sinon.spy(noop); - const newDate = new Date(2010, 10, 10); - - this.reinitFixture({ - onValueChanged: onValueChangedHandler, - applyValueMode: 'useButtons', - pickerType: 'calendar' - }); - this.fixture.dateBox.open(); - getInstanceWidget(this.fixture.dateBox).option('value', newDate); - $(APPLY_BUTTON_SELECTOR).eq(0).trigger('dxclick'); - assert.equal(this.fixture.dateBox.option('opened'), false); - assert.deepEqual(this.fixture.dateBox.option('value'), newDate); - assert.ok(onValueChangedHandler.calledOnce); - }); - - QUnit.test('Click on cancel button', function(assert) { - const onValueChangedHandler = sinon.spy(noop); - const oldDate = new Date(2008, 8, 8); - const newDate = new Date(2010, 10, 10); - - this.reinitFixture({ - value: oldDate, - onValueChanged: onValueChangedHandler, - applyValueMode: 'useButtons', - pickerType: 'calendar' - }); - - this.fixture.dateBox.open(); - getInstanceWidget(this.fixture.dateBox).option('value', newDate); - $('.dx-popup-cancel.dx-button').eq(0).trigger('dxclick'); - - assert.equal(this.fixture.dateBox.option('opened'), false); - assert.equal(this.fixture.dateBox.option('value'), oldDate); - assert.ok(!onValueChangedHandler.calledOnce); - }); - - QUnit.test('calendar does not open on field click (T189394)', function(assert) { - assert.ok(!this.fixture.dateBox.option('openOnFieldClick')); - }); - - const getLongestCaptionIndex = uiDateUtils.getLongestCaptionIndex; - const getLongestDate = uiDateUtils.getLongestDate; - - QUnit.test('getLongestDate must consider the possibility of overflowing to the next month from its 28th day and thus losing the longest month name when calculating widths for formats containing day and month names', function(assert) { - const someLanguageMonthNames = ['1', '1', '1', '1', '1', '1', '1', '1', '1', '22', '1', '1']; - const someLanguageDayNames = ['1', '1', '1', '1', '22', '1', '1']; - const longestMonthNameIndex = getLongestCaptionIndex(someLanguageMonthNames); - const longestDate = getLongestDate('D', someLanguageMonthNames, someLanguageDayNames); - assert.strictEqual(longestDate.getMonth(), longestMonthNameIndex); - }); - - QUnit.test('Calendar should update it value accordingly \'text\' option if it is valid (T189474)', function(assert) { - const date = new Date(2014, 5, 10); - - this.reinitFixture({ - value: date, - pickerType: 'calendar' - }); - - this.fixture.dateBox.open(); - - keyboardMock(this.fixture.input) - .caret(9) - .press('backspace') - .type('5'); - - this.fixture.input.trigger('change'); - this.fixture.dateBox.open(); - - const calendar = getInstanceWidget(this.fixture.dateBox); - assert.deepEqual(calendar.option('value'), new Date(2015, 5, 10)); - }); - - QUnit.test('Calendar should not be closed after datebox value has been changed by input', function(assert) { - const date = new Date(2014, 5, 10); - - this.reinitFixture({ - value: date, - applyValueMode: 'useButtons', - pickerType: 'calendar' - }); - - this.fixture.dateBox.open(); - - keyboardMock(this.fixture.input) - .caret(9) - .press('backspace') - .type('5'); - - this.fixture.input.trigger('change'); - - const calendar = getInstanceWidget(this.fixture.dateBox); - assert.deepEqual(calendar.option('value'), new Date(2015, 5, 10)); - assert.ok(this.fixture.dateBox.option('opened')); - }); - - QUnit.test('Value should be changed only after click on \'Apply\' button if the \'applyValueMode\' options is changed to \'useButtons\'', function(assert) { - const value = new Date(2015, 0, 20); - const newValue = new Date(2015, 0, 30); - - const dateBox = this.fixture.dateBox; - - dateBox.option('value', value); - dateBox.open(); - dateBox.close(); - dateBox.option('applyValueMode', 'useButtons'); - - dateBox.open(); - const calendar = getInstanceWidget(dateBox); - const $applyButton = dateBox._popup.$wrapper().find(APPLY_BUTTON_SELECTOR).eq(0); - - calendar.option('value', newValue); - assert.deepEqual(dateBox.option('value'), value, 'value is not changed yet'); - - $($applyButton).trigger('dxclick'); - assert.deepEqual(dateBox.option('value'), newValue, 'value is changed after click'); - }); - - QUnit.test('Value should be changed if it was entered from keyboard and it is out of range', function(assert) { - const value = new Date(2015, 0, 15); - const min = new Date(2015, 0, 10); - const max = new Date(2015, 0, 20); - - this.reinitFixture({ - value, - min, - max, - pickerType: 'calendar' - }); - - const dateBox = this.fixture.dateBox; - const $input = $(dateBox.$element().find(`.${TEXTEDITOR_INPUT_CLASS}`)); - const kb = keyboardMock($input); - const inputValue = '1/5/2015'; - - clearInput($input, kb); - kb.type(inputValue).change(); - assert.equal($input.val(), inputValue, 'input value is correct'); - assert.deepEqual(dateBox.option('value'), value, 'value has not been changed'); - assert.ok(!dateBox.option('isValid'), 'datebox value is invalid'); - - const validationError = dateBox.option('validationError'); - assert.ok(validationError, 'Validation error should be specified'); - assert.ok(validationError.editorSpecific, 'editorSpecific flag should be added'); - }); - - QUnit.test('Empty value should not be marked as \'out of range\'', function(assert) { - const value = new Date(2015, 0, 15); - const min = new Date(2015, 0, 10); - const max = new Date(2015, 0, 20); - - this.reinitFixture({ - value, - min, - max, - pickerType: 'calendar' - }); - - const dateBox = this.fixture.dateBox; - const $input = $(dateBox.$element().find(`.${TEXTEDITOR_INPUT_CLASS}`)); - const kb = keyboardMock($input); - - clearInput($input, kb); - kb.change(); - assert.ok(dateBox.option('isValid'), 'isValid flag should be set'); - assert.ok(!dateBox.option('validationError'), 'validationError should not be set'); - }); - - QUnit.test('Popup should not be hidden after value change using keyboard', function(assert) { - const value = new Date(2015, 0, 29); - - this.reinitFixture({ - type: 'date', - value, - pickerType: 'calendar' - }); - - const dateBox = this.fixture.dateBox; - const $input = $(dateBox.$element().find(`.${TEXTEDITOR_INPUT_CLASS}`)); - const kb = keyboardMock($input); - - dateBox.open(); - assert.equal($input.val(), '1/29/2015', 'correct input value'); - - kb - .caret(9) - .press('backspace') - .type('6') - .change(); - - assert.equal($input.val(), '1/29/2016', 'input value is changed'); - assert.ok(dateBox.option('opened'), 'popup is still opened'); - }); - - QUnit.test('T196443 - dxDateBox should not hide popup after erase date in input field', function(assert) { - const value = new Date(2015, 0, 30); - - this.reinitFixture({ - value, - min: null, - max: null, - type: 'date', - pickerType: 'calendar' - }); - - const dateBox = this.fixture.dateBox; - const $input = dateBox._input(); - const kb = keyboardMock($input); - - dateBox.open(); - kb.press('end'); - - for(let i = 0; i < 10; i++) { - kb.press('backspace'); - } - - assert.deepEqual(dateBox.option('value'), value, 'datebox value is not changed'); - assert.ok(dateBox.option('opened'), 'popup is still opened'); - }); - - QUnit.test('T203457 - popup should be closed when selected date is clicked', function(assert) { - const value = new Date(2015, 1, 1); - - this.reinitFixture({ - value, - min: null, - max: null, - type: 'date', - pickerType: 'calendar' - }); - - const dateBox = this.fixture.dateBox; - dateBox.open(); - const $selectedDate = dateBox._popup.$wrapper().find('.dx-calendar-selected-date'); - $($selectedDate).trigger('dxclick'); - - assert.ok(!dateBox.option('opened'), 'popup is closed'); - }); - - QUnit.test('T208825 - tapping on the \'enter\' should change value if popup is opened', function(assert) { - const value = new Date(2015, 2, 13); - - this.reinitFixture({ - value, - focusStateEnabled: true, - pickerType: 'calendar' - }); - - const dateBox = this.fixture.dateBox; - const $input = dateBox._input(); - const kb = keyboardMock($input); - - dateBox.option('valueChangeEvent', 'keyup'); - dateBox.open(); - - kb - .caret(9) - .press('backspace') - .type('4') - .press('enter'); - - assert.deepEqual(dateBox.option('value'), new Date(2014, 2, 13), 'value is changed'); - }); - - QUnit.test('Close popup on the \'enter\' press after input value is changed', function(assert) { - const value = new Date(2015, 2, 10); - - this.reinitFixture({ - value, - focusStateEnabled: true, - pickerType: 'calendar' - }); - - const dateBox = this.fixture.dateBox; - - dateBox.open(); - - keyboardMock(dateBox._input()) - .press('end') - .press('backspace') - .type('4') - .press('enter'); - - assert.equal(dateBox.option('opened'), false, 'popup is still opened'); - }); - - QUnit.test('repaint was fired if strategy is fallback', function(assert) { - this.reinitFixture({ - useNative: false, - useCalendar: true, - type: 'datetime', - pickerType: 'calendarWithTime', - opened: true - }); - - const dateBox = this.fixture.dateBox; - const popup = dateBox.$element().find('.dx-popup').dxPopup('instance'); - const repaintSpy = sinon.spy(popup, 'repaint'); - - this.clock.tick(10); - - assert.ok(repaintSpy.called, 'repaint was fired on opened'); - }); - - QUnit.test('changing type from \'datetime\' to \'date\' should lead to strategy changing', function(assert) { - this.reinitFixture({ - type: 'datetime', - pickerType: 'calendar' - }); - - const dateBox = this.fixture.dateBox; - assert.equal(dateBox._strategy.NAME, 'CalendarWithTime', 'correct strategy for the \'datetime\' type'); - - dateBox.option('type', 'date'); - assert.equal(dateBox._strategy.NAME, 'Calendar', 'correct strategy for the \'date\' type'); - }); - - QUnit.test('T247493 - value is cleared when text is changed to invalid date and popup is opened', function(assert) { - const date = new Date(2015, 5, 9); - - this.reinitFixture({ - pickerType: 'calendar', - value: date - }); - - const dateBox = this.fixture.dateBox; - const $element = $(dateBox.$element()); - const $input = $element.find(`.${TEXTEDITOR_INPUT_CLASS}`); - const kb = keyboardMock($input); - - kb - .press('end') - .press('backspace'); - - dateBox.open(); - assert.deepEqual(dateBox.option('value'), date, 'value is correct'); - assert.equal($input.val(), '6/9/201', 'input value is correct'); - }); - - QUnit.test('T252170 - date time should be the same with set value after calendar value is changed', function(assert) { - const date = new Date(2015, 5, 9, 15, 54, 13); - - this.reinitFixture({ - pickerType: 'calendar', - type: 'date', - value: date - }); - - const dateBox = this.fixture.dateBox; - dateBox.open(); - const calendar = dateBox._strategy._widget; - const $calendar = $(calendar.$element()); - - $($calendar.find('.dx-calendar-cell[data-value=\'2015/06/10\']')).trigger('dxclick'); - assert.deepEqual(calendar.option('value'), new Date(2015, 5, 10, 15, 54, 13), 'new calendar value saves set value time'); - assert.deepEqual(dateBox.option('value'), new Date(2015, 5, 10, 15, 54, 13), 'new datebox value saves set value time'); - }); - - QUnit.test('calendar views should be positioned correctly', function(assert) { - $('#dateBox').dxDateBox({ - type: 'date', - pickerType: 'calendar', - value: new Date(2015, 4, 12), - opened: true - }); - - const $calendarViews = $('.dx-popup-wrapper .dx-calendar-views-wrapper .dx-widget'); - const viewWidth = $calendarViews.eq(0).width(); - - assert.equal($calendarViews.eq(0).position().left, 0, 'main view is at 0'); - assert.equal($calendarViews.eq(1).position().left, -viewWidth, 'before view is at the left'); - assert.equal($calendarViews.eq(2).position().left, viewWidth, 'after view is at the right'); - }); - - QUnit.test('Popup with calendar strategy should be use \'flipfit flip\' strategy', function(assert) { - const $dateBox = $('#dateBox').dxDateBox({ - type: 'date', - pickerType: 'calendar', - value: new Date(), - deferRendering: true - }); - - $dateBox.dxDateBox('option', 'popupPosition', { my: 'bottom left' }); - - $dateBox.dxDateBox('option', 'opened', true); - - const popup = $dateBox.find('.dx-popup').dxPopup('instance'); - - assert.equal(popup.option('position').collision, 'flipfit flip', 'collision set correctly'); - assert.equal(popup.option('position').my, 'bottom left', 'position is saved'); - }); - - QUnit.test('Popup with calendarWithTime strategy should be use \'flipfit flip\' strategy', function(assert) { - const $dateBox = $('#dateBox').dxDateBox({ - type: 'datetime', - pickerType: 'calendar', - value: new Date(), - opened: true - }); - - assert.equal($dateBox.find('.dx-popup').dxPopup('option', 'position').collision, 'flipfit flip', 'collision set correctly'); - }); - - QUnit.test('DateBox should not take current date value at the opening if value is null', function(assert) { - const $dateBox = $('#dateBox').dxDateBox({ - value: null, - pickerType: 'calendar' - }); - - const instance = $dateBox.dxDateBox('instance'); - const $dropDownButton = $dateBox.find(`.${DROP_DOWN_BUTTON_CLASS}`); - - $($dropDownButton).trigger('dxclick'); - - assert.equal(instance.option('value'), null, 'value shouldn\'t be dropped after opening'); - }); - - QUnit.test('time component should not be changed if editing value with the help of keyboard (T398429)', function(assert) { - this.reinitFixture({ - type: 'date', - pickerType: 'calendar', - value: new Date(2016, 6, 1, 14, 15), - focusStateEnabled: true - }); - - keyboardMock(this.fixture.rootElement.find('.' + TEXTEDITOR_INPUT_CLASS)) - .focus() - .caret(2) - .press('del') - .type('2') - .change(); - - const value = this.fixture.dateBox.option('value'); - assert.equal(value.getHours(), 14, 'the \'hours\' component has not been changed'); - assert.equal(value.getMinutes(), 15, 'the \'minutes\' component has not been changed'); - }); - - QUnit.test('Calendar should have single selectionMode even if another selectionMode is passed to calendarOptions', function(assert) { - this.reinitFixture({ - pickerType: 'calendar', - opened: true, - calendarOptions: { - selectionMode: 'range', - } - }); - - assert.strictEqual(this.fixture.dateBox.option('calendarOptions.selectionMode'), 'single'); - }); -}); - -QUnit.module('datebox with time component', { - beforeEach: function() { - fx.off = true; - }, - afterEach: function() { - fx.off = false; - } -}, () => { - QUnit.test('date box should contain calendar and time view inside box in large screen', function(assert) { - const originalWidthFunction = implementationsMap.getWidth; - - try { - sinon.stub(implementationsMap, 'getWidth').returns(600); - - const $element = $('#dateBox').dxDateBox({ - type: 'datetime', - pickerType: 'calendar', - adaptivityEnabled: true, - opened: true - }); - - const instance = $element.dxDateBox('instance'); - const $content = $(instance._popup.$content()); - const box = Box.getInstance($content.find('.' + BOX_CLASS)); - const $clock = $content.find('.dx-timeview-clock'); - - assert.equal(box.option('direction'), 'row', 'correct box direction specified'); - assert.ok(box.itemElements().eq(0).find('.' + CALENDAR_CLASS).length, 'calendar rendered'); - assert.ok(box.itemElements().eq(1).find('.' + TIMEVIEW_CLASS).length, 'timeview rendered'); - assert.equal($clock.length, 1, 'clock was rendered'); - } finally { - implementationsMap.getWidth = originalWidthFunction; - } - }); - - QUnit.test('date box should contain calendar and time view inside box in small screen', function(assert) { - const originalWidthFunction = implementationsMap.getWidth; - - try { - sinon.stub(implementationsMap, 'getWidth').returns(300); - - const $element = $('#dateBox').dxDateBox({ - type: 'datetime', - pickerType: 'calendar', - adaptivityEnabled: true, - opened: true - }); - - const instance = $element.dxDateBox('instance'); - const $content = $(instance._popup.$content()); - const box = Box.getInstance($content.find('.' + BOX_CLASS)); - const $clock = $content.find('.dx-timeview-clock'); - - assert.equal(box.option('direction'), 'row', 'correct box direction specified'); - assert.ok(box.itemElements().eq(0).find('.' + CALENDAR_CLASS).length, 'calendar rendered'); - assert.ok(box.itemElements().eq(0).find('.' + TIMEVIEW_CLASS).length, 'timeview rendered'); - assert.equal($clock.length, 0, 'clock was not rendered'); - } finally { - implementationsMap.getWidth = originalWidthFunction; - } - }); - - [true, false].forEach((adaptivityEnabledValue) => { - QUnit.test(`date box should change behavior if adaptivityEnabled option is changed to ${adaptivityEnabledValue} at runtime`, function(assert) { - const widthStub = sinon.stub(implementationsMap, 'getWidth').returns(300); - - try { - const $element = $('#dateBox').dxDateBox({ - type: 'datetime', - pickerType: 'calendar', - adaptivityEnabled: !adaptivityEnabledValue, - opened: true - }); - const instance = $element.dxDateBox('instance'); - - instance.option('adaptivityEnabled', adaptivityEnabledValue); - instance.close(); - instance.open(); - - const $content = $(instance._popup.$content()); - const box = Box.getInstance($content.find(`.${BOX_CLASS}`)); - const $clock = $content.find(`.${TIMEVIEW_CLOCK_CLASS}`); - const timeViewExpectedMessage = `timeview is ${adaptivityEnabledValue ? '' : 'not'} rendered`; - const clockExpectedMessage = `clock is ${adaptivityEnabledValue ? 'not' : ''} rendered`; - - assert.strictEqual(box.itemElements().eq(0).find(`.${TIMEVIEW_CLASS}`).length, (adaptivityEnabledValue ? 1 : 0), timeViewExpectedMessage); - assert.strictEqual($clock.length, (adaptivityEnabledValue ? 0 : 1), clockExpectedMessage); - } finally { - widthStub.restore(); - } - }); - }); - - [true, false].forEach((showAnalogClockValue) => { - const timeViewExpectedMessage = `timeview is ${showAnalogClockValue ? 'not' : ''} rendered`; - const clockExpectedMessage = `clock is ${showAnalogClockValue ? '' : 'not'} rendered`; - - QUnit.test(`date box should ${showAnalogClockValue ? 'not' : ''} have compact view when showAnalogClock option is ${showAnalogClockValue}`, function(assert) { - const $element = $('#dateBox').dxDateBox({ - type: 'datetime', - pickerType: 'calendar', - showAnalogClock: showAnalogClockValue - }); - - const instance = $element.dxDateBox('instance'); - instance.open(); - - const $content = $(instance._popup.$content()); - const box = Box.getInstance($content.find(`.${BOX_CLASS}`)); - const $clock = $content.find('.dx-timeview-clock'); - - assert.strictEqual(box.option('direction'), 'row', 'correct box direction specified'); - assert.strictEqual(box.itemElements().eq(0).find(`.${CALENDAR_CLASS}`).length, 1, 'calendar rendered'); - assert.strictEqual(box.itemElements().eq(0).find(`.${TIMEVIEW_CLASS}`).length, (showAnalogClockValue ? 0 : 1), timeViewExpectedMessage); - assert.strictEqual($clock.length, (showAnalogClockValue ? 1 : 0), clockExpectedMessage); - }); - - QUnit.test(`date box should change behavior if showAnalogClock option is changed to ${showAnalogClockValue} at runtime`, function(assert) { - const $element = $('#dateBox').dxDateBox({ - type: 'datetime', - pickerType: 'calendar', - showAnalogClock: !showAnalogClockValue, - opened: true - }); - const instance = $element.dxDateBox('instance'); - - instance.option('showAnalogClock', showAnalogClockValue); - instance.close(); - instance.open(); - - const $content = $(instance._popup.$content()); - const box = Box.getInstance($content.find(`.${BOX_CLASS}`)); - const $clock = $content.find(`.${TIMEVIEW_CLOCK_CLASS}`); - assert.strictEqual(box.itemElements().eq(0).find(`.${TIMEVIEW_CLASS}`).length, (showAnalogClockValue ? 0 : 1), timeViewExpectedMessage); - assert.strictEqual($clock.length, (showAnalogClockValue ? 1 : 0), clockExpectedMessage); - }); - }); - - QUnit.test('date box wrapper adaptivity class depends on the screen size', function(assert) { - const LARGE_SCREEN_SIZE = 2000; - const SMALL_SCREEN_SIZE = 300; - - let stub = sinon.stub(implementationsMap, 'getWidth').returns(LARGE_SCREEN_SIZE); - - try { - const instance = $('#dateBox').dxDateBox({ - type: 'datetime', - pickerType: 'calendar', - adaptivityEnabled: true, - opened: true - }).dxDateBox('instance'); - - assert.notOk(instance._popup.$wrapper().hasClass(DATEBOX_ADAPTIVITY_MODE_CLASS), 'there is no adaptivity class for the large screen'); - - instance.close(); - - stub.restore(); - stub = sinon.stub(implementationsMap, 'getWidth').returns(SMALL_SCREEN_SIZE); - - instance.open(); - assert.ok(instance._popup.$wrapper().hasClass(DATEBOX_ADAPTIVITY_MODE_CLASS), 'there is the adaptivity class for the small screen'); - } finally { - stub.restore(); - } - }); - - QUnit.test('dateBox with datetime strategy should be rendered once on init', function(assert) { - const contentReadyHandler = sinon.spy(); - - $('#dateBox').dxDateBox({ - type: 'datetime', - pickerType: 'calendar', - onContentReady: contentReadyHandler - }).dxDateBox('instance'); - - assert.equal(contentReadyHandler.callCount, 1, 'contentReady has been called once'); - }); - - QUnit.test('date box popup should have maximum 100% width', function(assert) { - const currentDevice = sinon.stub(devices, 'current').returns({ - platform: 'generic', - phone: true - }); - - const clock = sinon.useFakeTimers(); - try { - const instance = $('#dateBox').dxDateBox({ - type: 'date', - pickerType: 'rollers', - opened: true - }).dxDateBox('instance'); - - assert.equal(instance._popup.option('maxWidth'), '100%', 'popup width should be correct on 320px screens'); - assert.equal(instance._popup.option('maxHeight'), '100%', 'popup height should be correct on 320px screens'); - } finally { - clock.restore(); - currentDevice.restore(); - } - }); - - QUnit.test('buttons are rendered after \'type\' option was changed', function(assert) { - const $element = $('#dateBox').dxDateBox({ - pickerType: 'calendar', - type: 'datetime', - applyValueMode: 'useButtons' - }); - - const dateBox = $element.dxDateBox('instance'); - - dateBox.open(); - - let $buttons = $('.dx-datebox-wrapper .dx-toolbar .dx-button'); - - assert.equal($buttons.length, 3, 'buttons are rendered'); - - dateBox.option('type', 'date'); - dateBox.open(); - dateBox.option('type', 'datetime'); - dateBox.open(); - - $buttons = $('.dx-datebox-wrapper .dx-toolbar .dx-button'); - assert.equal($buttons.length, 3, 'buttons are rendered after option was changed'); - }); - - QUnit.test('DateBox should have time part when pickerType is rollers', function(assert) { - const date = new Date(2015, 1, 1, 12, 13, 14); - const dateBox = $('#dateBox').dxDateBox({ - pickerType: 'rollers', - type: 'datetime', - value: date - }).dxDateBox('instance'); - - const format = uiDateUtils.FORMATS_MAP['datetime']; - const $input = $(dateBox.$element().find('.' + TEXTEDITOR_INPUT_CLASS)); - - assert.equal($input.val(), dateLocalization.format(date, format), 'input value is correct'); - }); - - QUnit.test('DateBox with pickerType=rollers should scroll to the neighbor item independent of deltaY when device is desktop (T921228)', function(assert) { - const date = new Date(2015, 0, 1); - $('#dateBox').dxDateBox({ - pickerType: 'rollers', - value: date, - opened: true - }); - - const $monthRollerView = $('.dx-dateviewroller-month'); - const monthRollerView = $monthRollerView.dxDateViewRoller('instance'); - const deltaY = 100; - const pointer = pointerMock(monthRollerView.container()); - - assert.strictEqual(monthRollerView.option('selectedIndex'), 0, 'selectedItem is correct'); - - pointer.start().wheel(deltaY).wait(500); - assert.strictEqual(monthRollerView.option('selectedIndex'), 0, 'selectedItem is correct'); - - pointer.start().wheel(-deltaY).wait(500); - assert.strictEqual(monthRollerView.option('selectedIndex'), 1, 'selectedItem is correct'); - - pointer.start().wheel(-deltaY * 3).wait(500); - assert.strictEqual(monthRollerView.option('selectedIndex'), 2, 'selectedItem is correct'); - - pointer.start().wheel(deltaY * 5).wait(500); - assert.strictEqual(monthRollerView.option('selectedIndex'), 1, 'selectedItem is correct'); - - pointer.start().wheel(-deltaY * 10).wait(500); - assert.strictEqual(monthRollerView.option('selectedIndex'), 2, 'selectedItem is correct'); - }); - - QUnit.test('dateview selectedIndex should not be changed after dateBox reopen (T934663)', function(assert) { - assert.expect(0); - - const clock = sinon.useFakeTimers(); - try { - const date = new Date(2015, 3, 3); - const dateBox = $('#dateBox').dxDateBox({ - pickerType: 'rollers', - value: date, - opened: true - }).dxDateBox('instance'); - - const selectedIndexChangedHandler = (args) => { - assert.ok(false, 'selectedIndex has been changed'); - }; - - const monthRollerView = $('.dx-dateviewroller-month').dxDateViewRoller('instance'); - const dayRollerView = $('.dx-dateviewroller-day').dxDateViewRoller('instance'); - const yearRollerView = $('.dx-dateviewroller-year').dxDateViewRoller('instance'); - monthRollerView.option('onSelectedIndexChanged', selectedIndexChangedHandler); - dayRollerView.option('onSelectedIndexChanged', selectedIndexChangedHandler); - yearRollerView.option('onSelectedIndexChanged', selectedIndexChangedHandler); - - dateBox.close(); - dateBox.open(); - } finally { - clock.restore(); - } - }); - - QUnit.test('DateBox with time should be rendered correctly when templatesRenderAsynchronously=true', function(assert) { - const clock = sinon.useFakeTimers(); - try { - const dateBox = $('#dateBox').dxDateBox({ - type: 'datetime', - pickerType: 'calendar', - value: new Date(), - templatesRenderAsynchronously: true - }).dxDateBox('instance'); - - dateBox.option('opened', true); - clock.tick(10); - - const $content = $(dateBox._popup.$content()); - const $timeView = $content.find('.dx-timeview-clock'); - assert.ok($timeView.parent().width() > 100, 'Time view was rendered correctly'); - } finally { - clock.restore(); - } - }); - - QUnit.test('DateBox renders the right stylingMode for editors in time view overlay (default)', function(assert) { - const dateBox = $('#dateBox').dxDateBox({ - type: 'datetime', - value: new Date('2015/1/25') - }).dxDateBox('instance'); - - dateBox.open(); - - const hourEditor = $(`.${TIMEVIEW_CLASS} .${NUMBERBOX_CLASS}`).eq(0); - const minuteEditor = $(`.${TIMEVIEW_CLASS} .${NUMBERBOX_CLASS}`).eq(1); - const amPmEditor = $(`.${TIMEVIEW_CLASS} .${SELECTBOX_CLASS}`).eq(0); - - assert.ok(hourEditor.hasClass('dx-editor-outlined')); - assert.ok(minuteEditor.hasClass('dx-editor-outlined')); - assert.ok(amPmEditor.hasClass('dx-editor-outlined')); - }); - - QUnit.test('DateBox renders the right stylingMode for editors in time view overlay (custom)', function(assert) { - const dateBox = $('#dateBox').dxDateBox({ - type: 'datetime', - value: new Date('2015/1/25'), - stylingMode: 'underlined' - }).dxDateBox('instance'); - - dateBox.open(); - - const hourEditor = $(`.${TIMEVIEW_CLASS} .${NUMBERBOX_CLASS}`).eq(0); - const minuteEditor = $(`.${TIMEVIEW_CLASS} .${NUMBERBOX_CLASS}`).eq(1); - const amPmEditor = $(`.${TIMEVIEW_CLASS} .${SELECTBOX_CLASS}`).eq(0); - - assert.ok(hourEditor.hasClass('dx-editor-underlined')); - assert.ok(minuteEditor.hasClass('dx-editor-underlined')); - assert.ok(amPmEditor.hasClass('dx-editor-underlined')); - }); - - QUnit.test('DateBox with timeview should have amPm popup inside of dateBox popup content (T1300566)', function(assert) { - const dateBox = $('#dateBox').dxDateBox({ - type: 'datetime', - pickerType: 'calendar', - opened: true, - displayFormat: 'ddMMyy hh:mm', - }).dxDateBox('instance'); - const amPmEditor = $(`.${TIMEVIEW_CLASS} .${SELECTBOX_CLASS}`).eq(0).dxSelectBox('instance'); - - amPmEditor.open(); - - const $dateBoxPopup = $(dateBox.content()); - const $amPmPopup = $(amPmEditor.content()); - - assert.strictEqual($amPmPopup.closest($dateBoxPopup).length, 1, 'amPm popup is inside dateBox popup'); - }); - - QUnit.test('Reset seconds and milliseconds when DateBox has no value for time view', function(assert) { - const dateBox = $('#dateBox').dxDateBox({ - pickerType: 'list', - type: 'time' - }).dxDateBox('instance'); - - dateBox.open(); - - $('.dx-list-item').first().trigger('dxclick'); - - assert.equal(dateBox.option('value').getSeconds(), 0, 'seconds has zero value'); - assert.equal(dateBox.option('value').getMilliseconds(), 0, 'milliseconds has zero value'); - }); - - QUnit.module('value change', { - beforeEach: function() { - this.currentDateTime = new Date(2001, 1, 1, 1, 1, 0, 0); - this.clock = sinon.useFakeTimers(this.currentDateTime.valueOf()); - - this.date = new Date('2015/1/25'); - this.init = (options) => { - this.$dateBox = $('#dateBox').dxDateBox($.extend({}, { - type: 'datetime', - value: this.date, - opened: true, - pickerType: 'calendar', - }, options)); - this.dateBox = this.$dateBox.dxDateBox('instance'); - this.$input = this.$dateBox.find(`.${TEXTEDITOR_INPUT_CLASS}`); - this.$submitInput = this.$dateBox.find('input[type=hidden]'); - this.$content = $(this.dateBox.content()); - this.keyboard = keyboardMock(this.$input); - this.timeView = this.$content - .find(`.${TIMEVIEW_CLASS}`) - .dxTimeView('instance'); - this.calendar = this.$content - .find(`.${CALENDAR_CLASS}`) - .dxCalendar('instance'); - - this.$hourBox = this.$content.find(`.${NUMBERBOX_CLASS}`).eq(0); - this.$hourBoxSpinDown = this.$hourBox.find(`.${NUMBERBOX_SPIN_DOWN_CLASS}`).eq(0); - - this.$minuteBox = this.$content.find(`.${NUMBERBOX_CLASS}`).eq(1); - this.$minuteBoxSpinDown = this.$minuteBox.find(`.${NUMBERBOX_SPIN_DOWN_CLASS}`).eq(0); - }; - this.reinit = (options = {}) => { - this.dateBox.dispose(); - this.init(options); - }; - this.clickApplyButton = () => { - $(APPLY_BUTTON_SELECTOR).first().trigger('dxclick'); - }; - this.clickCalendarCell = () => { - $(`.${CALENDAR_CELL_CLASS}`).first().trigger('dxclick'); - }; - - this.init({}); - }, - afterEach: function() { - this.clock.restore(); - } - }, () => { - QUnit.test('dateBox should update time on enter pressing (T969012)', function(assert) { - const expectedDate = new Date(this.date); - expectedDate.setHours(11, 28); - - this.timeView.option('value', expectedDate); - this.keyboard - .focus() - .press('enter'); - - assert.deepEqual(this.dateBox.option('value'), expectedDate, 'dateBox value was updated'); - }); - - QUnit.test('dateBox should update date and time on enter pressing after time value change using arrows', function(assert) { - const time = new Date(this.date); - time.setHours(11, 28); - - this.keyboard.press('up'); - this.timeView.option('value', time); - this.keyboard - .focus() - .press('enter') - .press('enter'); - - const expectedDate = new Date(time); - expectedDate.setDate(18); - - assert.deepEqual(this.dateBox.option('value'), expectedDate, 'dateBox value was updated'); - }); - - QUnit.test('submit value should be updated after apply button click if internal validation is failed', function(assert) { - const date = new Date(new Date('2015/1/25 13:00:00')); - - this.reinit({ - min: date, - value: date - }); - - this.$hourBoxSpinDown.trigger('dxpointerdown'); - this.clickApplyButton(); - - assert.notOk(this.dateBox.option('isValid'), 'editor is invalid'); - assert.strictEqual(this.$submitInput.val(), '2015-01-25T12:00:00', 'submit element has correct value'); - }); - - QUnit.test('submit value should be updated after apply button click if external validation is failed', function(assert) { - this.date = new Date('2015/1/25 13:00:00'); - this.reinit(); - this.$dateBox.dxValidator({ - validationRules: [{ - type: 'custom', - reevaluate: true, - validationCallback: function(e) { - return false; - } - }] - }); - - this.$hourBoxSpinDown.trigger('dxpointerdown'); - this.clickApplyButton(); - - assert.notOk(this.dateBox.option('isValid'), 'editor is invalid'); - assert.strictEqual(this.$submitInput.val(), '2015-01-25T12:00:00', 'submit element has correct value'); - }); - - QUnit.test('invalid (by internal validation) value should be displayed if it is selected by time numberboxes (T939117)', function(assert) { - const date = new Date('2015/1/25 14:00:00'); - this.reinit({ - min: date, - displayFormat: 'HH:mm', - value: date - }); - - this.$hourBoxSpinDown.trigger('dxpointerdown'); - this.clickApplyButton(); - - assert.strictEqual(this.$input.val(), '13:00', 'input displays a correct value'); - assert.strictEqual(this.dateBox.option('text'), '13:00', 'text is invalid'); - assert.notOk(this.dateBox.option('isValid'), 'widget is invalid'); - - assert.deepEqual(this.dateBox.option('value'), new Date(date.setHours(13)), 'value does not changed'); - }); - - QUnit.test('datebox value is bound to time view value', function(assert) { - let date = new Date(2014, 2, 1, 14, 33); - this.dateBox.option('value', date); - assert.equal(this.timeView.option('value').getTime(), date.getTime(), 'timeView value is set'); - - date = new Date(2014, 2, 1, 17, 47); - this.timeView.option('value', date); - - this.clickApplyButton(); - - assert.strictEqual(this.dateBox.option('value').toString(), date.toString(), 'dateBox value is set'); - }); - - QUnit.test('time value should be updated after select date', function(assert) { - this.calendar.option('value', new Date(2014, 2, 1, 11, 15)); - this.timeView.option('value', new Date(2014, 1, 1, 12, 16)); - - this.clickApplyButton(); - - const expectedValue = (new Date(2014, 2, 1, 12, 16)).toString(); - assert.strictEqual(this.dateBox.option('value').toString(), expectedValue, 'dateBox value is set'); - }); - - QUnit.test('Reset seconds and milliseconds when DateBox has no value for datetime view', function(assert) { - this.reinit({ - min: new Date('2015/1/25'), - max: new Date('2015/2/10') - }); - - this.clickCalendarCell(); - this.clickApplyButton(); - - assert.strictEqual(this.dateBox.option('value').getSeconds(), 0, 'seconds has zero value'); - assert.strictEqual(this.dateBox.option('value').getMilliseconds(), 0, 'milliseconds has zero value'); - }); - - QUnit.test('time is reset when calendar value is changed (T208853)', function(assert) { - this.date = new Date(2015, 1, 16, 11, 20); - this.reinit(); - - this.calendar.option('value', new Date(2014, 1, 16)); - this.clickApplyButton(); - - assert.deepEqual(this.dateBox.option('value'), new Date(2014, 1, 16, 11, 20), 'date and time are correct'); - }); - - ['instantly', 'useButtons'].forEach(applyValueMode => { - QUnit.module(`the out of range value, applyValueMode: ${applyValueMode}`, { - beforeEach: function() { - this.date = new Date('2015/1/25 14:00:00'); - this.onValueChangedHandler = sinon.stub(); - - this.reinit({ - type: 'datetime', - min: this.date, - max: this.date, - value: this.date, - applyValueMode, - useMaskBehavior: true, - onValueChanged: this.onValueChangedHandler - }); - }, - afterEach: function() { - this.onValueChangedHandler.reset(); - } - }, () => { - QUnit.test('valueChangeEvent should be called after change of datebox value', function(assert) { - this.dateBox.option('value', new Date('2015/1/25 13:00:00')); - - assert.strictEqual(this.onValueChangedHandler.callCount, applyValueMode === 'instantly' ? 3 : 1, 'onValueChangedHandler.callCount'); - assert.strictEqual(this.$input.val(), '1/25/2015, 1:00 PM', 'input displays a correct value'); - - const { text, isValid, value } = this.dateBox.option(); - assert.strictEqual(text, '1/25/2015, 1:00 PM', 'text is right'); - assert.strictEqual(isValid, false, 'widget is invalid'); - assert.deepEqual(value, new Date(this.date.setHours(13)), 'value is changed correctly'); - }); - - QUnit.test('valueChangeEvent should be called after change of hours by spin click', function(assert) { - this.$hourBoxSpinDown.trigger('dxpointerdown'); - this.clickApplyButton(); - - assert.strictEqual(this.onValueChangedHandler.callCount, 1, 'onValueChangedHandler.callCount'); - assert.strictEqual(this.$input.val(), '1/25/2015, 1:00 PM', 'input displays a correct value'); - - const { text, isValid, value } = this.dateBox.option(); - assert.strictEqual(text, '1/25/2015, 1:00 PM', 'text is right'); - assert.strictEqual(isValid, false, 'widget is invalid'); - assert.deepEqual(value, new Date(this.date.setHours(13)), 'value is changed correctly'); - }); - - QUnit.test('valueChangeEvent should be called after change of minutes by spin click', function(assert) { - this.$minuteBoxSpinDown.trigger('dxpointerdown'); - this.clickApplyButton(); - - assert.strictEqual(this.onValueChangedHandler.callCount, 1, 'onValueChangedHandler.callCount'); - assert.strictEqual(this.$input.val(), '1/25/2015, 2:59 PM', 'input displays a correct value'); - - const { text, isValid, value } = this.dateBox.option(); - assert.strictEqual(text, '1/25/2015, 2:59 PM', 'text is right'); - assert.strictEqual(isValid, false, 'widget is invalid'); - assert.deepEqual(value, new Date(this.date.setMinutes(59)), 'value is changed correctly'); - }); - - QUnit.test('valueChangeEvent should be called after input in hours', function(assert) { - const keyboard = keyboardMock(this.$hourBox.find(`.${TEXTEDITOR_INPUT_CLASS}`)); - keyboard - .focus() - .caret(this.$input.val().length - 3) - .press('backspace') - .press('backspace') - .type('13') - .change(); - - if(applyValueMode === 'useButtons') { - this.clickApplyButton(); - } - - assert.strictEqual(this.onValueChangedHandler.callCount, 1, 'onValueChangedHandler.callCount'); - assert.strictEqual(this.$input.val(), '1/25/2015, 1:00 PM', 'input displays a correct value'); - - const { text, isValid, value } = this.dateBox.option(); - assert.strictEqual(text, '1/25/2015, 1:00 PM', 'text is right'); - assert.strictEqual(isValid, false, 'widget is invalid'); - assert.deepEqual(value, new Date(this.date.setHours(13)), 'value is changed correctly'); - }); - - QUnit.test('valueChangeEvent should be called after input in minutes', function(assert) { - const keyboard = keyboardMock(this.$minuteBox.find(`.${TEXTEDITOR_INPUT_CLASS}`)); - keyboard - .focus() - .caret(this.$input.val().length - 3) - .press('backspace') - .press('backspace') - .type('59') - .change(); - - if(applyValueMode === 'useButtons') { - this.clickApplyButton(); - } - - assert.strictEqual(this.onValueChangedHandler.callCount, 1, 'onValueChangedHandler.callCount'); - assert.strictEqual(this.$input.val(), '1/25/2015, 2:59 PM', 'input displays a correct value'); - - const { text, isValid, value } = this.dateBox.option(); - assert.strictEqual(text, '1/25/2015, 2:59 PM', 'text is right'); - assert.strictEqual(isValid, false, 'widget is invalid'); - assert.deepEqual(value, new Date(this.date.setMinutes(59)), 'value is changed correctly'); - }); - }); - }); - - QUnit.module('partial datetime selecting when value=null', { - beforeEach: function() { - this.reinit({ - value: null - }); - } - }, () => { - QUnit.test('updated value time should be equal to current time if only date is selected (T231015)', function(assert) { - const newDateTime = new Date(2002, 2, 2, 1, 1, 0, 0); - - this.calendar.option('value', new Date(2002, 2, 2, 14, 17, 22, 34)); // updates only date - this.clickApplyButton(); - - assert.strictEqual(this.dateBox.option('value').getTime(), newDateTime.getTime(), 'value is correct if only calendar value is changed'); - }); - - QUnit.test('updated value time should be equal to selected time if only time is selected (T231015)', function(assert) { - const newDateTime = new Date(2001, 1, 1, 2, 2, 0, 0); - - this.timeView.option('value', new Date(2002, 2, 2, 2, 2)); // updated only time - this.clickApplyButton(); - - assert.strictEqual(this.dateBox.option('value').getTime(), newDateTime.getTime(), 'value is correct if only timeView value is changed'); - }); - - QUnit.test('updated value should have date and time equal to current date and time after apply button click if value is null (T253298)', function(assert) { - this.clickApplyButton(); - - assert.strictEqual(this.dateBox.option('value').getDate(), this.currentDateTime.getDate(), 'value date is correct'); - assert.strictEqual(this.dateBox.option('value').getTime(), this.currentDateTime.getTime(), 'value time is correct'); - }); - - QUnit.test('updated value should have date equal to calendar controured date after apply button click if value is null (T1039021)', function(assert) { - const minDate = new Date(2050, 10, 10); - this.reinit({ - value: null, - min: minDate - }); - - this.clickApplyButton(); - - const expectedDateTime = new Date(minDate); - expectedDateTime.setHours(this.currentDateTime.getHours(), this.currentDateTime.getMinutes()); - - assert.strictEqual(this.dateBox.option('value').getDate(), expectedDateTime.getDate(), 'value date is correct'); - assert.strictEqual(this.dateBox.option('value').getTime(), expectedDateTime.getTime(), 'value time is correct'); - }); - }); - }); -}); - -QUnit.module('datebox w/ time list', { - before: function() { - this.checkForIncorrectKeyWarning = function(assert) { - const isIncorrectKeyWarning = logger.warn.lastCall.args[0].indexOf('W1002') > -1; - assert.ok(logger.warn.calledOnce); - assert.ok(isIncorrectKeyWarning); - }; - }, - beforeEach: function() { - fx.off = true; - - this.$dateBox = $('#dateBox'); - - this.dateBox = this.$dateBox - .dxDateBox({ - pickerType: 'list', - type: 'time' - }) - .dxDateBox('instance'); - }, - afterEach: function() { - fx.off = false; - } -}, () => { - QUnit.test('rendered markup', function(assert) { - this.dateBox.option('opened', true); - assert.ok($(DATEBOX_LIST_POPUP_SELECTOR).length, 'Popup has dx-timebox-popup-wrapper class'); - }); - - QUnit.test('rendered popup markup', function(assert) { - this.dateBox.option('opened', true); - - assert.ok(this.dateBox._popup, 'popup exist'); - }); - - QUnit.test('rendered list markup', function(assert) { - this.dateBox.option('opened', true); - - assert.ok(getInstanceWidget(this.dateBox), 'list exist'); - assert.ok(getInstanceWidget(this.dateBox).$element().hasClass(LIST_CLASS), 'list initialized'); - }); - - QUnit.test('list should contain correct values if min/max does not specified', function(assert) { - this.dateBox.option({ - min: null, - max: null - }); - - this.dateBox.option('opened', true); - - const $timeList = $(`.${LIST_CLASS}`); - const $listItems = $timeList.find('.dx-list-item-content'); - - assert.equal($listItems.first().text(), '12:00 AM', 'min value is right'); - assert.equal($listItems.last().text(), '11:30 PM', 'max value is right'); - }); - - QUnit.test('list should contain all correct values when min/max options are defined (T869203)', function(assert) { - this.dateBox.option({ - min: new Date(2015, 11, 1, 5, 45), - max: new Date(2015, 11, 1, 6, 15), - interval: 15 - }); - - this.dateBox.option('opened', true); - - const $timeList = $(`.${LIST_CLASS}`); - const $listItems = $timeList.find('.dx-list-item-content'); - - assert.strictEqual($listItems.first().text(), '5:45 AM', 'min value is right'); - assert.strictEqual($listItems.last().text(), '6:15 AM', 'max value is right'); - assert.strictEqual($listItems.length, 3, 'list items count is correct'); - }); - - QUnit.test('min/max option test', function(assert) { - this.dateBox.option({ - min: new Date(2008, 7, 8, 4, 0), - max: new Date(2008, 7, 8, 8, 59) - }); - - this.dateBox.option('opened', true); - - const $timeList = $(`.${LIST_CLASS}`); - const $listItems = $timeList.find('.dx-list-item-content'); - - assert.equal($listItems.first().text(), '4:00 AM', 'min value is right'); - assert.equal($listItems.last().text(), '8:30 AM', 'max value is right'); - }); - - QUnit.test('min/max overflow test', function(assert) { - this.dateBox.option({ - min: new Date(2008, 7, 8, 4, 0), - max: new Date(2008, 7, 9, 9, 0) - }); - - this.dateBox.option('opened', true); - - const $timeList = $(`.${LIST_CLASS}`); - const $listItems = $timeList.find('.dx-list-item-content'); - - assert.strictEqual($listItems.first().text(), '4:00 AM', 'min value is right'); - assert.strictEqual($listItems.last().text(), '4:00 AM', 'max value is right'); - }); - - QUnit.test('interval option', function(assert) { - this.dateBox.option({ - min: new Date(2008, 7, 8, 4, 0), - value: new Date(2008, 7, 8, 5, 0), - max: new Date(2008, 7, 8, 6, 0), - interval: 60 - }); - - this.dateBox.option('opened', true); - - let $timeList = $(`.${LIST_CLASS}`); - let items = $timeList.find(LIST_ITEM_SELECTOR); - - assert.strictEqual(items.length, 3, 'interval option works'); - - this.dateBox.option('interval', 120); - this.dateBox.option('opened', true); - - $timeList = $(`.${LIST_CLASS}`); - items = $timeList.find(LIST_ITEM_SELECTOR); - - assert.strictEqual(items.length, 2, 'interval option works'); - }); - - QUnit.test('T240639 - correct list item should be highlighted if appropriate datebox value is set', function(assert) { - sinon.stub(logger, 'warn'); - try { - this.dateBox.option({ - type: 'time', - pickerType: 'list', - value: new Date(0, 0, 0, 12, 30), - opened: true - }); - - const list = this.dateBox._strategy._widget; - - assert.deepEqual(list.option('selectedIndex'), 25, 'selectedIndex item is correct'); - assert.deepEqual(list.option('selectedItem'), new Date(0, 0, 0, 12, 30), 'selected list item is correct'); - - this.dateBox.option('value', new Date(2016, 1, 1, 12, 20)); - - this.checkForIncorrectKeyWarning(assert); - assert.equal(list.option('selectedIndex'), -1, 'there is no selected list item'); - assert.equal(list.option('selectedItem'), null, 'there is no selected list item'); - } finally { - logger.warn.restore(); - } - }); - - QUnit.test('T351678 - the date is reset after item click', function(assert) { - this.dateBox.option({ - type: 'time', - pickerType: 'list', - value: new Date(2020, 4, 13, 12, 17), - opened: true - }); - - const $list = $(this.dateBox._strategy._widget.$element()); - $($list.find('.dx-list-item').eq(3)).trigger('dxclick'); - - assert.deepEqual(this.dateBox.option('value'), new Date(2020, 4, 13, 1, 30), 'date is correct'); - }); - - QUnit.test('the date should be in range after the selection', function(assert) { - this.dateBox.option({ - type: 'time', - pickerType: 'list', - min: new Date(2016, 10, 5, 12, 0, 0), - max: new Date(2016, 10, 5, 14, 0, 0), - opened: true - }); - - const $item = $(this.dateBox.content()).find('.dx-list-item').eq(0); - - $item.trigger('dxclick'); - - assert.deepEqual(this.dateBox.option('value'), new Date(2016, 10, 5, 12, 0, 0), 'date is correct'); - }); - - QUnit.test('list should have items if the \'min\' option is specified (T395529)', function(assert) { - this.dateBox.option({ - min: new Date(new Date(null).setHours(15)), - opened: true - }); - - const list = $(`.${LIST_CLASS}`).dxList('instance'); - assert.ok(list.option('items').length > 0, 'list is not empty'); - }); - - QUnit.test('selected date should be in 1970 when it was set from the null value', function(assert) { - this.dateBox.option({ - opened: true, - value: null - }); - - const $item = $(this.dateBox.content()).find('.dx-list-item').eq(0); - $item.trigger('dxclick'); - - assert.strictEqual(this.dateBox.option('value').getFullYear(), new Date(null).getFullYear(), 'year is correct'); - }); - - QUnit.test('selected date should be in value year when value is specified', function(assert) { - this.dateBox.option({ - opened: true, - value: new Date(2018, 5, 6, 14, 12) - }); - - const $item = $(this.dateBox.content()).find('.dx-list-item').eq(0); - $item.trigger('dxclick'); - - assert.strictEqual(this.dateBox.option('value').getFullYear(), 2018, 'year is correct'); - }); - - QUnit.test('selected date should be in 1970 when it was set from user\'s input', function(assert) { - this.dateBox.option({ - value: null, - displayFormat: 'HH:mm' - }); - - keyboardMock(this.$dateBox.find(`.${TEXTEDITOR_INPUT_CLASS}`)) - .focus() - .type('11:11') - .change(); - - assert.strictEqual(this.dateBox.option('value').getFullYear(), new Date(null).getFullYear(), 'year is correct'); - }); - - QUnit.test('the value\'s date part should not be changed if editing input\'s text by keyboard (T395685)', function(assert) { - this.dateBox.option({ - focusStateEnabled: true, - value: new Date(2016, 5, 25, 14, 22) - }); - - const $input = this.$dateBox.find('.' + TEXTEDITOR_INPUT_CLASS); - keyboardMock($input) - .focus() - .caret($input.val().length - 3) - .press('backspace') - .press('backspace') - .type('44') - .change(); - - assert.deepEqual(this.dateBox.option('value'), new Date(2016, 5, 25, 14, 44), 'value is correct'); - }); - - QUnit.test('List of items should be refreshed after value is changed', function(assert) { - this.dateBox.option({ - min: new Date(2016, 1, 1, 10, 0), - value: new Date(2016, 1, 2, 14, 45), - interval: 60, - opened: true - }); - - const $timeList = $(`.${LIST_CLASS}`); - let items = $timeList.find(LIST_ITEM_SELECTOR); - - assert.equal(items.length, 24, '24 items should be find'); - - this.dateBox.option('value', new Date(2016, 1, 1)); - - items = $timeList.find(LIST_ITEM_SELECTOR); - - assert.equal(items.length, 14, '14 items should be find from min to finish of day'); - }); - - QUnit.test('All items in list should be present if value and min options are belong to different days', function(assert) { - const clock = sinon.useFakeTimers(); - sinon.stub(logger, 'warn'); - try { - this.dateBox.option({ - min: new Date(2016, 1, 1, 13, 45), - value: new Date(2016, 1, 1, 14, 45), - interval: 60, - opened: true - }); - - const $timeList = $(`.${LIST_CLASS}`); - let items = $timeList.find(LIST_ITEM_SELECTOR); - - assert.equal(items.length, 11, 'interval option works'); - - this.dateBox.option('value', new Date(2016, 1, 2, 13, 45)); - - items = $timeList.find(LIST_ITEM_SELECTOR); - - this.checkForIncorrectKeyWarning(assert); - assert.equal(items.length, 24, 'interval is correct'); - assert.equal(items.eq(0).text(), '12:45 AM', 'start time is correct'); - } finally { - clock.restore(); - logger.warn.restore(); - } - }); - - QUnit.test('The situation when value and max options are belong to one day', function(assert) { - this.dateBox.option({ - value: new Date(2016, 1, 1, 13, 45), - max: new Date(2016, 1, 1, 15, 0), - interval: 60, - opened: true - }); - - const $timeList = $(`.${LIST_CLASS}`); - const items = $timeList.find(LIST_ITEM_SELECTOR); - - assert.strictEqual(items.length, 16, 'list should be contain right count of items'); - }); - - QUnit.test('value and max are belong to one day', function(assert) { - this.dateBox.option({ - min: new Date(2016, 1, 1, 0, 11), - value: new Date(2016, 1, 3, 14, 45), - max: new Date(2016, 1, 3, 18, 22), - interval: 60, - opened: true - }); - - const $timeList = $(`.${LIST_CLASS}`); - const items = $timeList.find(LIST_ITEM_SELECTOR); - - assert.equal(items.length, 19, 'list should be contain right count of items'); - assert.equal(items.eq(0).text(), '12:11 AM', 'first item in list is correct'); - assert.equal(items.eq(items.length - 1).text(), '6:11 PM', 'last item in list is correct'); - }); - - QUnit.test('List items should be started with minimal possible value', function(assert) { - this.dateBox.option({ - min: new Date(2016, 1, 1, 0, 17), - value: new Date(2016, 1, 3, 14, 45), - interval: 15, - opened: true - }); - - const $timeList = $(`.${LIST_CLASS}`); - const items = $timeList.find(LIST_ITEM_SELECTOR); - - assert.equal(items.eq(0).text(), '12:02 AM', 'first item in list is correct'); - assert.equal(items.eq(items.length - 1).text(), '11:47 PM', 'last item in list is correct'); - }); - - QUnit.test('dxDateBox with list strategy automatically scrolls to selected item on opening', function(assert) { - this.dateBox.option({ - value: new Date(2016, 1, 3, 14, 45), - interval: 15, - opened: true - }); - - this.dateBox.option('opened', true); - - const $popupContent = $('.dx-popup-content'); - const $selectedItem = $popupContent.find('.' + LIST_ITEM_SELECTED_CLASS); - - assert.ok($popupContent.offset().top + $popupContent.height() > $selectedItem.offset().top, 'selected item is visible'); - }); - - QUnit.test('min/max settings should be work if value option is null', function(assert) { - this.dateBox.option({ - value: null, - min: new Date(2008, 7, 8, 8, 0), - max: new Date(2008, 7, 8, 20, 0) - }); - - this.dateBox.option('opened', true); - - const $timeList = $(`.${LIST_CLASS}`); - const $listItems = $timeList.find('.dx-list-item-content'); - - assert.strictEqual($listItems.first().text(), '8:00 AM', 'min value is right'); - assert.strictEqual($listItems.last().text(), '8:00 PM', 'max value is right'); - }); - - QUnit.test('min/max settings should be work if value option is undefined', function(assert) { - this.dateBox.option({ - value: undefined, - min: new Date(2008, 7, 8, 8, 0), - max: new Date(2008, 7, 8, 20, 0) - }); - - this.dateBox.option('opened', true); - - const $timeList = $(`.${LIST_CLASS}`); - const $listItems = $timeList.find('.dx-list-item-content'); - - assert.strictEqual($listItems.first().text(), '8:00 AM', 'min value is right'); - assert.strictEqual($listItems.last().text(), '8:00 PM', 'max value is right'); - }); - - QUnit.test('validator correctly check value with \'time\' format', function(assert) { - sinon.stub(logger, 'warn'); - try { - const $dateBox = $('#dateBox').dxDateBox({ - type: 'time', - pickerType: 'list', - min: new Date(2015, 1, 1, 6, 0), - max: new Date(2015, 1, 1, 16, 0), - value: new Date(2015, 1, 1, 12, 0), - opened: true - }); - - const dateBox = $dateBox.dxDateBox('instance'); - const $input = $dateBox.find('.' + TEXTEDITOR_INPUT_CLASS); - - $input.val('11:30 AM').change(); - - const value = dateBox.option('value'); - this.checkForIncorrectKeyWarning(assert); - assert.equal($input.val(), '11:30 AM', 'Correct input value'); - assert.equal(value.getHours(), 11, 'Correct hours'); - assert.equal(value.getMinutes(), 30, 'Correct minutes'); - assert.equal(dateBox.option('isValid'), true, 'Editor should be marked as valid'); - } finally { - logger.warn.restore(); - } - }); - - QUnit.testInActiveWindow('select a new value via the Enter key', function(assert) { - const $dateBox = $('#dateBox').dxDateBox({ - type: 'time', - value: new Date(2018, 2, 2, 12, 0, 13), - pickerType: 'list' - }); - - const dateBox = $dateBox.dxDateBox('instance'); - const $input = $dateBox.find('.' + TEXTEDITOR_INPUT_CLASS); - const keyboard = keyboardMock($input); - - $input.focusin(); - this.dateBox.option('opened', true); - keyboard - .keyDown('down') - .keyDown('down') - .keyDown('enter'); - - const value = dateBox.option('value'); - assert.equal($input.val(), '1:00 PM', 'Correct input value'); - assert.equal(value.getHours(), 13, 'Correct hours'); - assert.equal(value.getMinutes(), 0, 'Correct minutes'); - }); - - QUnit.test('items are rendered when value is \'undefined\' (T805931)', function(assert) { - this.dateBox.option({ - value: undefined - }); - - this.dateBox.option('opened', true); - - const $timeListItems = $('.dx-list .dx-list-item'); - assert.ok($timeListItems.length > 0); - }); - - QUnit.test('should works correctly with serialized dates (T854579)', function(assert) { - this.dateBox.option({ - opened: true, - dateSerializationFormat: 'yyyy-MM-ddTHH:mm:ssx', - }); - const $input = $(this.dateBox.element()).find(`.${TEXTEDITOR_INPUT_CLASS}`); - const $items = $(this.dateBox.content()).find(LIST_ITEM_SELECTOR); - - $items.eq(1).trigger('dxclick'); - assert.strictEqual($input.val(), $items.eq(1).text(), 'time is applied'); - - this.dateBox.open(); - $items.eq(3).trigger('dxclick'); - assert.strictEqual($input.val(), $items.eq(3).text(), 'new time is applied'); - }); - - QUnit.module('applyValueMode = useButtons', { - beforeEach: function() { - this.date = new Date(2020, 1, 1); - this.dateBox.option({ - value: this.date, - opened: true, - applyValueMode: 'useButtons' - }); - this.$items = $(this.dateBox.content()).find(LIST_ITEM_SELECTOR); - this.$firstItem = this.$items.eq(1); - } - }, () => { - QUnit.test('should not close popup on list item click', function(assert) { - this.$firstItem.trigger('dxclick'); - - assert.ok(this.dateBox.option('opened'), 'dateBox is still opened'); - }); - - QUnit.test('should not instantly select value on list item click (T1005111)', function(assert) { - this.$firstItem.trigger('dxclick'); - - assert.deepEqual(this.dateBox.option('value'), this.date, 'item is not selected'); - }); - - QUnit.test('should not raise validation error on "Ok" button click without item selecting (T1005111)', function(assert) { - $(APPLY_BUTTON_SELECTOR).trigger('dxclick'); - - assert.ok(this.dateBox.option('isValid'), 'dateBox is still valid'); - }); - - QUnit.test('should update value on "Ok" button click', function(assert) { - const expectedDate = new Date(this.date); - expectedDate.setHours(0, 30); - this.$firstItem.trigger('dxclick'); - $(APPLY_BUTTON_SELECTOR).trigger('dxclick'); - - assert.deepEqual(this.dateBox.option('value'), expectedDate, 'value is updated'); - }); - - QUnit.test('should not update value on "Cancel" button click', function(assert) { - this.$firstItem.trigger('dxclick'); - $(CANCEL_BUTTON_SELECTOR).trigger('dxclick'); - - assert.deepEqual(this.dateBox.option('value'), this.date, 'value is not updated'); - }); - - QUnit.testInActiveWindow('should not close on "tab" press', function(assert) { - const $input = this.$dateBox.find(`.${TEXTEDITOR_INPUT_CLASS}`); - const keyboard = keyboardMock($input); - - keyboard - .focus() - .keyDown('tab'); - - assert.ok(this.dateBox.option('opened'), 'dateBox is still opened'); - }); - }); -}); - - -QUnit.module('width of datebox with list', { - beforeEach: function() { - fx.off = true; - - this.$dateBox = $('#dateBox'); - }, - afterEach: function() { - fx.off = false; - } -}, () => { - QUnit.module('overlay content real width', () => { - QUnit.test('should be equal to the editor width when dropDownOptions.width in not defined', function(assert) { - this.$dateBox.dxDateBox({ - opened: true, - pickerType: 'list', - type: 'time' - }).dxDateBox('instance'); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - assert.strictEqual($overlayContent.outerWidth(), this.$dateBox.outerWidth(), 'popup width is correct'); - }); - - QUnit.test('should be equal to the editor width when dropDownOptions.width in not defined after editor width runtime change', function(assert) { - const dateBox = this.$dateBox.dxDateBox({ - opened: true, - pickerType: 'list', - type: 'time' - }).dxDateBox('instance'); - - dateBox.option('width', 153); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - assert.strictEqual($overlayContent.outerWidth(), this.$dateBox.outerWidth(), 'popup width is correct'); - }); - - QUnit.test('should be equal to dropDownOptions.width if it\'s defined (T897820)', function(assert) { - this.$dateBox.dxDateBox({ - type: 'time', - pickerType: 'list', - dropDownOptions: { - width: 500 - }, - opened: true - }); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - assert.strictEqual($overlayContent.outerWidth(), 500, 'overlay content width is correct'); - }); - - QUnit.test('should be equal to dropDownOptions.width even after editor input width change (T897820)', function(assert) { - const dateBox = this.$dateBox.dxDateBox({ - type: 'time', - pickerType: 'list', - dropDownOptions: { - width: 500 - }, - opened: true - }).dxDateBox('instance'); - - dateBox.option('width', 300); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - assert.strictEqual($overlayContent.outerWidth(), 500, 'overlay content width is correct'); - }); - - QUnit.test('should be equal to wrapper width if dropDownOptions.width is set to auto (T897820)', function(assert) { - this.$dateBox.dxDateBox({ - type: 'time', - pickerType: 'list', - dropDownOptions: { - width: 'auto' - }, - opened: true - }); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - const $overlayWrapper = $(`.${OVERLAY_WRAPPER_CLASS}`); - assert.strictEqual($overlayContent.outerWidth(), $overlayWrapper.outerWidth(), 'overlay content width is correct'); - }); - - QUnit.test('should be equal to wrapper width if dropDownOptions.width is set to 100%', function(assert) { - this.$dateBox.dxDateBox({ - type: 'time', - pickerType: 'list', - dropDownOptions: { - width: '100%' - }, - opened: true - }); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - const $overlayWrapper = $(`.${OVERLAY_WRAPPER_CLASS}`); - assert.strictEqual($overlayContent.outerWidth(), $overlayWrapper.outerWidth(), 'overlay content width is correct'); - }); - - QUnit.test('should be calculated relative to wrapper when dropDownOptions.width is percent (T897820)', function(assert) { - this.$dateBox.dxDateBox({ - type: 'time', - pickerType: 'list', - dropDownOptions: { - width: '50%' - }, - opened: true - }); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - const $overlayWrapper = $(`.${OVERLAY_WRAPPER_CLASS}`); - assert.roughEqual($overlayContent.outerWidth(), $overlayWrapper.outerWidth() / 2, 0.1, 'overlay content width is correct'); - }); - - QUnit.test('should be calculated relative to wrapper after editor width runtime change', function(assert) { - const dateBox = this.$dateBox.dxDateBox({ - type: 'time', - pickerType: 'list', - width: 600, - dropDownOptions: { - width: '50%' - }, - opened: true - }).dxDateBox('instance'); - - dateBox.option('width', 700); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - const $overlayWrapper = $(`.${OVERLAY_WRAPPER_CLASS}`); - assert.roughEqual($overlayContent.outerWidth(), $overlayWrapper.outerWidth() / 2, 0.1, 'overlay content width is correct'); - }); - - QUnit.test('should be equal to editor input width even when dropDownOptions.container is defined (T938497)', function(assert) { - this.$dateBox.dxDateBox({ - type: 'time', - pickerType: 'list', - dropDownOptions: { - container: $('#containerWithWidth') - }, - opened: true - }); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - - assert.strictEqual($overlayContent.outerWidth(), this.$dateBox.outerWidth(), 'width is correct'); - }); - }); - - QUnit.test('dropDownOptions.width should be passed to popup', function(assert) { - this.$dateBox.dxDateBox({ - type: 'time', - pickerType: 'list', - dropDownOptions: { - width: 500 - }, - opened: true - }); - - const popup = this.$dateBox.find(`.${POPUP_CLASS}`).dxPopup('instance'); - assert.strictEqual(popup.option('width'), 500, 'popup width option value is correct'); - }); - - QUnit.test('popup should have width equal to dropDownOptions.width even after editor input width change (T897820)', function(assert) { - const dateBox = this.$dateBox.dxDateBox({ - type: 'time', - pickerType: 'list', - dropDownOptions: { - width: 500 - }, - opened: true - }).dxDateBox('instance'); - - dateBox.option('width', 300); - - const popup = this.$dateBox.find(`.${POPUP_CLASS}`).dxPopup('instance'); - assert.strictEqual(popup.option('width'), 500, 'popup width option value is correct'); - }); -}); - -QUnit.module('height of datebox with list', { - beforeEach: function() { - fx.off = true; - - this.$dateBox = $('#dateBox'); - }, - afterEach: function() { - fx.off = false; - } -}, () => { - QUnit.module('overlay content height', () => { - QUnit.test('should be equal to 0.45 * window height when dropDownOptions.height in not defined and list hight is bigger than 0.45 of window height', function(assert) { - this.$dateBox.dxDateBox({ - opened: true, - pickerType: 'list', - type: 'time' - }).dxDateBox('instance'); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - assert.roughEqual($overlayContent.outerHeight(), 0.45 * $(window).outerHeight(), 0.1, 'overlay content height is correct'); - }); - - QUnit.test('should be equal to 0.45 * window height when dropDownOptions.height in set to auto and list hight is bigger than 0.45 of window height', function(assert) { - this.$dateBox.dxDateBox({ - opened: true, - pickerType: 'list', - type: 'time', - dropDownOptions: { - height: 'auto' - } - }).dxDateBox('instance'); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - assert.roughEqual($overlayContent.outerHeight(), 0.45 * $(window).outerHeight(), 0.1, 'overlay content height is correct'); - }); - - QUnit.test('should be equal to 0.45 * window height when dropDownOptions.height in not defined after editor height runtime change', function(assert) { - const dateBox = this.$dateBox.dxDateBox({ - opened: true, - pickerType: 'list', - type: 'time' - }).dxDateBox('instance'); - - dateBox.option('height', 153); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - assert.roughEqual($overlayContent.outerHeight(), 0.45 * $(window).outerHeight(), 0.1, 'overlay content height is correct'); - }); - - QUnit.test('should be equal to list height when dropDownOptions.height in not defined and content list is smaller than 0.45 of window height', function(assert) { - this.$dateBox.dxDateBox({ - opened: true, - pickerType: 'list', - type: 'time', - min: Date.now(), - max: Date.now(), - }).dxDateBox('instance'); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - const $list = $(`.${LIST_CLASS}`); - assert.strictEqual($overlayContent.outerHeight(), $list.outerHeight(), 'overlay content height is correct'); - }); - - QUnit.test('should be equal to list height when dropDownOptions.height in set to auto and content list is smaller than 0.45 of window height', function(assert) { - this.$dateBox.dxDateBox({ - opened: true, - pickerType: 'list', - type: 'time', - min: Date.now(), - max: Date.now(), - dropDownOptions: { - height: 'auto' - } - }).dxDateBox('instance'); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - const $list = $(`.${LIST_CLASS}`); - assert.strictEqual($overlayContent.outerHeight(), $list.outerHeight(), 'overlay content height is correct'); - }); - - QUnit.test('should be equal to dropDownOptions.height if it is defined', function(assert) { - const dropDownOptionsHeight = 200; - this.$dateBox.dxDateBox({ - type: 'time', - pickerType: 'list', - dropDownOptions: { - height: dropDownOptionsHeight - }, - opened: true - }); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - assert.strictEqual($overlayContent.outerHeight(), dropDownOptionsHeight, 'overlay content height is correct'); - }); - - QUnit.test('should be equal to dropDownOptions.height even after editor input height change', function(assert) { - const dropDownOptionsHeight = 500; - const dateBox = this.$dateBox.dxDateBox({ - type: 'time', - pickerType: 'list', - dropDownOptions: { - height: dropDownOptionsHeight - }, - opened: true - }).dxDateBox('instance'); - - dateBox.option('height', 300); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - assert.strictEqual($overlayContent.outerHeight(), dropDownOptionsHeight, 'overlay content height is correct'); - }); - - QUnit.test('should be equal to wrapper height if dropDownOptions.height is set to 100%', function(assert) { - this.$dateBox.dxDateBox({ - type: 'time', - pickerType: 'list', - dropDownOptions: { - height: '100%' - }, - opened: true - }); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - const $overlayWrapper = $(`.${OVERLAY_WRAPPER_CLASS}`); - assert.strictEqual($overlayContent.outerHeight(), $overlayWrapper.outerHeight(), 'overlay content height is correct'); - }); - - QUnit.test('should be calculated relative to wrapper when dropDownOptions.height is percent', function(assert) { - this.$dateBox.dxDateBox({ - type: 'time', - pickerType: 'list', - dropDownOptions: { - height: '50%' - }, - opened: true - }); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - const $overlayWrapper = $(`.${OVERLAY_WRAPPER_CLASS}`); - assert.roughEqual($overlayContent.outerHeight(), $overlayWrapper.outerHeight() / 2, 0.1, 'overlay content height is correct'); - }); - - QUnit.test('should be calculated relative to wrapper after editor height runtime change', function(assert) { - const dateBox = this.$dateBox.dxDateBox({ - type: 'time', - pickerType: 'list', - height: 600, - dropDownOptions: { - height: '50%' - }, - opened: true - }).dxDateBox('instance'); - - dateBox.option('height', 700); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - const $overlayWrapper = $(`.${OVERLAY_WRAPPER_CLASS}`); - assert.roughEqual($overlayContent.outerHeight(), $overlayWrapper.outerHeight() / 2, 0.1, 'overlay content height is correct'); - }); - }); - - QUnit.test('dropDownOptions.height should be passed to popup', function(assert) { - const dropDownOptionsHeight = 500; - this.$dateBox.dxDateBox({ - type: 'time', - pickerType: 'list', - dropDownOptions: { - height: dropDownOptionsHeight - }, - opened: true - }); - - const popup = this.$dateBox.find(`.${POPUP_CLASS}`).dxPopup('instance'); - assert.strictEqual(popup.option('height'), dropDownOptionsHeight, 'popup height option value is correct'); - }); - - QUnit.test('popup should have height equal to dropDownOptions.height even after editor input height change', function(assert) { - const dropDownOptionsHeight = 500; - const dateBox = this.$dateBox.dxDateBox({ - type: 'time', - pickerType: 'list', - dropDownOptions: { - height: dropDownOptionsHeight - }, - opened: true - }).dxDateBox('instance'); - - dateBox.option('height', 300); - - const popup = this.$dateBox.find(`.${POPUP_CLASS}`).dxPopup('instance'); - assert.strictEqual(popup.option('height'), dropDownOptionsHeight, 'popup height option value is correct'); - }); -}); - -QUnit.module('width of datebox with calendar', { - beforeEach: function() { - fx.off = true; - - this.$dateBox = $('#dateBox'); - }, - afterEach: function() { - fx.off = false; - } -}, () => { - QUnit.module('overlay content width', () => { - QUnit.test('should be equal to the calendar width + margins when dropDownOptions.width in not defined', function(assert) { - this.$dateBox.dxDateBox({ - opened: true, - pickerType: 'calendar' - }).dxDateBox('instance'); - - const $calendar = $(`.${CALENDAR_CLASS}`); - const paddingsWidth = parseInt($calendar.css('margin-left')) * 2; - const calendarWidth = $calendar.outerWidth() + paddingsWidth; - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - assert.strictEqual($overlayContent.width(), calendarWidth, 'popup width is correct'); - }); - - QUnit.test('should be equal to the calendar width + margins when dropDownOptions.width in not defined after editor width runtime change', function(assert) { - const dateBox = this.$dateBox.dxDateBox({ - opened: true, - pickerType: 'calendar' - }).dxDateBox('instance'); - - const $calendar = $(`.${CALENDAR_CLASS}`); - const paddingsWidth = parseInt($calendar.css('margin-left')) * 2; - const calendarWidth = $calendar.outerWidth() + paddingsWidth; - - dateBox.option('width', 153); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - assert.strictEqual($overlayContent.width(), calendarWidth, 'popup width is correct'); - }); - - QUnit.test('should be equal to dropDownOptions.width if it\'s defined', function(assert) { - this.$dateBox.dxDateBox({ - pickerType: 'calendar', - dropDownOptions: { - width: 500 - }, - opened: true - }); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - assert.strictEqual($overlayContent.outerWidth(), 500, 'overlay content width is correct'); - }); - - QUnit.test('should be equal to dropDownOptions.width even after editor input width change', function(assert) { - const dateBox = this.$dateBox.dxDateBox({ - pickerType: 'calendar', - dropDownOptions: { - width: 500 - }, - opened: true - }).dxDateBox('instance'); - - dateBox.option('width', 300); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - assert.strictEqual($overlayContent.outerWidth(), 500, 'overlay content width is correct'); - }); - - QUnit.test('should be equal to calendar width + margins if dropDownOptions.width is set to auto', function(assert) { - this.$dateBox.dxDateBox({ - pickerType: 'calendar', - dropDownOptions: { - width: 'auto' - }, - opened: true - }); - - const $calendar = $(`.${CALENDAR_CLASS}`); - const paddingsWidth = parseInt($calendar.css('margin-left')) * 2; - const calendarWidth = $calendar.outerWidth() + paddingsWidth; - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - assert.strictEqual($overlayContent.width(), calendarWidth, 'overlay content width is correct'); - }); - - QUnit.test('should be equal to wrapper width if dropDownOptions.width is set to 100%', function(assert) { - this.$dateBox.dxDateBox({ - pickerType: 'calendar', - dropDownOptions: { - width: '100%' - }, - opened: true - }); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - const $overlayWrapper = $(`.${OVERLAY_WRAPPER_CLASS}`); - assert.strictEqual($overlayContent.outerWidth(), $overlayWrapper.outerWidth(), 'overlay content width is correct'); - }); - - QUnit.test('should be calculated relative to wrapper when dropDownOptions.width is percent', function(assert) { - this.$dateBox.dxDateBox({ - pickerType: 'calendar', - dropDownOptions: { - width: '50%' - }, - opened: true - }); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - const $overlayWrapper = $(`.${OVERLAY_WRAPPER_CLASS}`); - assert.roughEqual($overlayContent.outerWidth(), $overlayWrapper.outerWidth() / 2, 0.1, 'overlay content width is correct'); - }); - - QUnit.test('should be calculated relative to wrapper after editor width runtime change', function(assert) { - const dateBox = this.$dateBox.dxDateBox({ - pickerType: 'calendar', - width: 600, - dropDownOptions: { - width: '50%' - }, - opened: true - }).dxDateBox('instance'); - - dateBox.option('width', 700); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - const $overlayWrapper = $(`.${OVERLAY_WRAPPER_CLASS}`); - assert.roughEqual($overlayContent.outerWidth(), $overlayWrapper.outerWidth() / 2, 0.1, 'overlay content width is correct'); - }); - - QUnit.test('should be equal to calendar width + margins even when dropDownOptions.container is defined', function(assert) { - this.$dateBox.dxDateBox({ - pickerType: 'calendar', - dropDownOptions: { - container: $('#containerWithWidth') - }, - opened: true - }); - - const $overlayContent = $(`.${OVERLAY_CONTENT_CLASS}`); - const $calendar = $(`.${CALENDAR_CLASS}`); - const paddingsWidth = parseInt($calendar.css('margin-left')) * 2; - const calendarWidth = $calendar.outerWidth() + paddingsWidth; - - assert.strictEqual($overlayContent.width(), calendarWidth, 'width is correct'); - }); - }); - - QUnit.test('dropDownOptions.width should be passed to popup', function(assert) { - this.$dateBox.dxDateBox({ - pickerType: 'calendar', - dropDownOptions: { - width: 500 - }, - opened: true - }); - - const popup = this.$dateBox.find(`.${POPUP_CLASS}`).dxPopup('instance'); - assert.strictEqual(popup.option('width'), 500, 'popup width option value is correct'); - }); - - QUnit.test('popup should have width equal to dropDownOptions.width even after editor input width change (T897820)', function(assert) { - const dateBox = this.$dateBox.dxDateBox({ - pickerType: 'calendar', - dropDownOptions: { - width: 500 - }, - opened: true - }).dxDateBox('instance'); - - dateBox.option('width', 300); - - const popup = this.$dateBox.find(`.${POPUP_CLASS}`).dxPopup('instance'); - assert.strictEqual(popup.option('width'), 500, 'popup width option value is correct'); - }); -}); - -if(devices.real().deviceType === 'desktop') { - QUnit.module('keyboard navigation', { - beforeEach: function() { - fx.off = true; - - this.$dateBox = $('#dateBox'); - - this.dateBox = this.$dateBox - .dxDateBox({ - pickerType: 'calendar', - type: 'time', - focusStateEnabled: true, - min: new Date(2008, 7, 8, 4, 30), - value: new Date(2008, 7, 8, 5, 0), - max: new Date(2008, 7, 8, 6, 0) - }) - .dxDateBox('instance'); - - this.$input = this.$dateBox.find(`.${TEXTEDITOR_INPUT_CLASS}`); - this.keyboard = keyboardMock(this.$input); - }, - afterEach: function() { - fx.off = false; - } - }, () => { - QUnit.testInActiveWindow('popup hides on tab', function(assert) { - this.$input.focusin(); - - assert.ok(this.$dateBox.hasClass(STATE_FOCUSED_CLASS), 'element is focused'); - this.dateBox.option('opened', true); - this.keyboard.keyDown('tab'); - assert.ok(this.$dateBox.hasClass(STATE_FOCUSED_CLASS), 'element is focused'); - - assert.equal(this.dateBox.option('opened'), false, 'popup is hidden'); - }); - - QUnit.testInActiveWindow('home/end should not be handled', function(assert) { - this.$input.focusin(); - this.dateBox.option('opened', true); - const $timeList = $(`.${LIST_CLASS}`); - - this.keyboard.keyDown('down'); - this.keyboard.keyDown('end'); - assert.ok(!$timeList.find(LIST_ITEM_SELECTOR).eq(0).hasClass(STATE_FOCUSED_CLASS), 'element is not focused'); - this.keyboard.keyDown('home'); - assert.ok(!$timeList.find(LIST_ITEM_SELECTOR).eq(0).hasClass(STATE_FOCUSED_CLASS), 'element is not focused'); - }); - - QUnit.testInActiveWindow('arrow keys control', function(assert) { - this.$input.focusin(); - this.dateBox.option('opened', true); - this.keyboard.keyDown('down'); - - const $timeList = $(`.${LIST_CLASS}`); - - assert.ok($timeList.find(LIST_ITEM_SELECTOR).eq(2).hasClass(STATE_FOCUSED_CLASS), 'correct item is focused'); - - this.keyboard.keyDown('down'); - assert.ok($timeList.find(LIST_ITEM_SELECTOR).eq(3).hasClass(STATE_FOCUSED_CLASS), 'correct item is focused'); - - this.keyboard.keyDown('down'); - assert.ok($timeList.find(LIST_ITEM_SELECTOR).eq(0).hasClass(STATE_FOCUSED_CLASS), 'correct item is focused'); - - this.keyboard.keyDown('up'); - assert.ok($timeList.find(LIST_ITEM_SELECTOR).eq(3).hasClass(STATE_FOCUSED_CLASS), 'correct item is focused'); - - this.keyboard.keyDown('enter'); - assert.strictEqual(this.dateBox.option('opened'), false, 'popup is hidden'); - - const selectedDate = this.dateBox.option('value'); - assert.strictEqual(selectedDate.getHours(), 6, 'hours is right'); - assert.strictEqual(selectedDate.getMinutes(), 0, 'minutes is right'); - }); - - QUnit.test('apply contoured date on enter for date and datetime mode', function(assert) { - this.dateBox = this.$dateBox - .dxDateBox({ - pickerType: 'calendar', - type: 'date', - applyValueMode: 'useButtons', - focusStateEnabled: true, - min: new Date(2008, 6, 8, 4, 30), - value: new Date(2008, 7, 8, 5, 0), - max: new Date(2008, 9, 8, 6, 0), - opened: true - }) - .dxDateBox('instance'); - - const $input = this.$dateBox.find(`.${TEXTEDITOR_INPUT_CLASS}`); - - $($input).trigger($.Event('keydown', { key: 'ArrowUp' })); - $($input).trigger($.Event('keydown', { key: 'ArrowDown' })); - $($input).trigger($.Event('keydown', { key: 'ArrowUp' })); - $($input).trigger($.Event('keydown', { key: 'Enter' })); - - assert.equal(this.dateBox.option('opened'), false, 'popup is hidden'); - - const selectedDate = this.dateBox.option('value'); - assert.equal(selectedDate.getDate(), 1, 'day is right'); - }); - - QUnit.test('Enter key press prevents default when popup in opened', function(assert) { - assert.expect(1); - - let prevented = 0; - - const $dateBox = $('
').appendTo('body').dxDateBox({ - pickerType: 'calendar', - focusStateEnabled: true, - value: new Date(2015, 5, 13), - opened: true - }); - - const $input = $dateBox.find(`.${TEXTEDITOR_INPUT_CLASS}`); - const keyboard = keyboardMock($input); - - try { - $($dateBox).on('keydown', e => { - if(e.isDefaultPrevented()) { - prevented++; - } - }); - - keyboard.keyDown('enter'); - assert.equal(prevented, 1, 'defaults prevented on enter key press'); - - } finally { - $dateBox.remove(); - } - }); - - QUnit.testInActiveWindow('the \'shift+tab\' key press leads to the cancel button focus if the input is focused', function(assert) { - this.dateBox.option({ - pickerType: 'calendar', - type: 'datetime', - opened: true, - applyValueMode: 'useButtons' - }); - - const $input = this.$dateBox.find(`.${TEXTEDITOR_INPUT_CLASS}`); - - $input - .focus() - .trigger($.Event('keydown', { - key: 'Tab', - shiftKey: true - })); - - const $cancelButton = this.dateBox._popup.$wrapper().find('.dx-button.dx-popup-cancel'); - assert.ok($cancelButton.hasClass('dx-state-focused'), 'cancel button is focused'); - }); - - QUnit.test('pressing tab should set focus on calendar prev button in popup', function(assert) { - this.dateBox.option({ - pickerType: 'calendar', - type: 'date', - applyValueMode: 'useButtons', - opened: true, - min: null, - max: null, - }); - - const $input = this.$dateBox.find(`.${TEXTEDITOR_INPUT_CLASS}`); - - $input - .focus() - .trigger($.Event('keydown', { - key: 'Tab', - })); - - const $prevButton = this.dateBox._popup.$wrapper().find(`.${CALENDAR_NAVIGATOR_PREVIOUS_VIEW_CLASS}`); - - assert.ok($prevButton.hasClass(STATE_FOCUSED_CLASS)); - }); - - QUnit.test('pressing tab should set focus on first item in popup with custom items', function(assert) { - this.dateBox.option({ - pickerType: 'calendar', - type: 'date', - applyValueMode: 'useButtons', - opened: true, - dropDownOptions: { - toolbarItems: [{ - widget: 'dxButton', - toolbar: 'top', - location: 'before', - options: { - text: 'Button', - }, - }, - { - widget: 'dxTextBox', - toolbar: 'bottom', - location: 'before', - options: { - text: 'Text box', - }, - }], - }, - }); - const $input = this.$dateBox.find(`.${TEXTEDITOR_INPUT_CLASS}`); - - $input - .focus() - .trigger($.Event('keydown', { - key: 'Tab', - })); - - const $firstItem = this.dateBox._popup.$wrapper().find(BUTTON_SELECTOR); - assert.ok($firstItem.hasClass(STATE_FOCUSED_CLASS)); - }); - - QUnit.test('pressing tab + shift should set focus on last item in popup with custom items', function(assert) { - this.dateBox.option({ - pickerType: 'calendar', - type: 'date', - applyValueMode: 'useButtons', - opened: true, - dropDownOptions: { - toolbarItems: [{ - widget: 'dxButton', - toolbar: 'top', - location: 'before', - options: { - text: 'Button', - }, - }, - { - widget: 'dxTextBox', - toolbar: 'bottom', - location: 'before', - options: { - text: 'Text box', - }, - }], - }, - }); - const $input = this.$dateBox.find(`.${TEXTEDITOR_INPUT_CLASS}`); - - $input - .focus() - .trigger($.Event('keydown', { - key: 'Tab', - shiftKey: true - })); - - const $lastItem = this.dateBox._popup.$wrapper().find(TEXTBOX_SELECTOR); - assert.ok($lastItem.hasClass(STATE_FOCUSED_CLASS)); - }); - - QUnit.test('Home and end key press prevent default when popup in opened (T587313)', function(assert) { - assert.expect(1); - - let prevented = 0; - - this.dateBox.option('opened', true); - - this.$dateBox.on('keydown', (e) => { - if(e.isDefaultPrevented()) { - prevented++; - } - }); - - this.keyboard.keyDown('home'); - this.keyboard.keyDown('end'); - - assert.equal(prevented, 0, 'defaults prevented on home and end keys'); - }); - - QUnit.test('Home and end key press does not prevent default when popup in not opened (T587313)', function(assert) { - assert.expect(1); - - let prevented = 0; - - this.dateBox.option('opened', false); - - this.$dateBox.on('keydown', (e) => { - if(e.isDefaultPrevented()) { - prevented++; - } - }); - - this.keyboard.keyDown('home'); - this.keyboard.keyDown('end'); - - assert.equal(prevented, 0, 'defaults has not prevented on home and end keys'); - }); - - QUnit.testInActiveWindow('Unsupported key handlers must be processed correctly', function(assert) { - this.dateBox.option({ - pickerType: 'list', - type: 'time' - }); - - const $input = this.$dateBox.find(`.${TEXTEDITOR_INPUT_CLASS}`); - const keyboard = keyboardMock($input); - - this.dateBox.focus(); - - let isNoError = true; - - try { - keyboard - .press('down') - .press('up') - .press('right') - .press('left'); - } catch(e) { - isNoError = false; - } - - assert.ok(isNoError, 'key handlers processed without errors'); - }); - - QUnit.test('Pressing escape when focus \'today\' button must hide the popup', function(assert) { + [ + { editorName: 'hour', editorIndex: 0 }, + { editorName: 'minute', editorIndex: 1 }, + { editorName: 'period', editorIndex: 2 } + ].forEach(({ editorName, editorIndex }) => { + QUnit.test(`Pressing escape when focus the ${editorName} editor must hide the popup`, function(assert) { const escapeKeyDown = $.Event('keydown', { key: 'Escape' }); this.dateBox.option({ - type: 'date', pickerType: 'calendar', - applyValueMode: 'useButtons' + type: 'datetime' }); this.dateBox.open(); $(this.dateBox.content()) - .parent() - .find('.dx-button-today') + .find(`.${TEXTEDITOR_INPUT_CLASS}`) + .eq(editorIndex) .trigger(escapeKeyDown); assert.ok(!this.dateBox.option('opened')); }); + }); - [ - { editorName: 'hour', editorIndex: 0 }, - { editorName: 'minute', editorIndex: 1 }, - { editorName: 'period', editorIndex: 2 } - ].forEach(({ editorName, editorIndex }) => { - QUnit.test(`Pressing escape when focus the ${editorName} editor must hide the popup`, function(assert) { - const escapeKeyDown = $.Event('keydown', { key: 'Escape' }); - this.dateBox.option({ - pickerType: 'calendar', - type: 'datetime' - }); - this.dateBox.open(); - - $(this.dateBox.content()) - .find(`.${TEXTEDITOR_INPUT_CLASS}`) - .eq(editorIndex) - .trigger(escapeKeyDown); - - assert.ok(!this.dateBox.option('opened')); - }); + QUnit.test('DateBox value is applied after the second press of the "Enter" key', function(assert) { + this.dateBox.option({ + pickerType: 'calendar', + type: 'datetime', + applyValueMode: 'useButtons', + focusStateEnabled: true, + value: null, + opened: true }); - QUnit.test('DateBox value is applied after the second press of the "Enter" key', function(assert) { - this.dateBox.option({ - pickerType: 'calendar', - type: 'datetime', - applyValueMode: 'useButtons', - focusStateEnabled: true, - value: null, - opened: true - }); - - const instance = this.dateBox; - const $content = $(instance.content()); - const $input = $(instance.element()).find(`.${TEXTEDITOR_INPUT_CLASS}`); - const keyboard = keyboardMock($input); - function getValue() { - return instance.option('value'); - } - function calendarHasSelectedDate() { - return $content.find('.dx-calendar-selected-date').length > 0; - } + const instance = this.dateBox; + const $content = $(instance.content()); + const $input = $(instance.element()).find(`.${TEXTEDITOR_INPUT_CLASS}`); + const keyboard = keyboardMock($input); + function getValue() { + return instance.option('value'); + } + function calendarHasSelectedDate() { + return $content.find('.dx-calendar-selected-date').length > 0; + } - assert.notOk(getValue()); - assert.notOk(calendarHasSelectedDate()); + assert.notOk(getValue()); + assert.notOk(calendarHasSelectedDate()); - keyboard.press('enter'); + keyboard.press('enter'); - assert.notOk(getValue(), 'value does not applied to the DateBox after the first press of the "Enter" key'); - assert.ok(calendarHasSelectedDate(), 'but Calendar got selected date'); + assert.notOk(getValue(), 'value does not applied to the DateBox after the first press of the "Enter" key'); + assert.ok(calendarHasSelectedDate(), 'but Calendar got selected date'); - keyboard.press('enter'); - assert.ok(getValue(), 'DateBox got selected value after the second press of the "Enter" key'); - }); + keyboard.press('enter'); + assert.ok(getValue(), 'DateBox got selected value after the second press of the "Enter" key'); }); -} +}); QUnit.module('Popup open state', () => { ['date', 'time'].forEach(type => { @@ -6486,26 +3383,3 @@ QUnit.module('validation', { assert.strictEqual(this.$dateBox.hasClass(SHOW_INVALID_BADGE_CLASS), false, 'validation icon is be hidden'); }); }); - -QUnit.module('Device specific tests', { - beforeEach: function() { - this._savedDevice = devices.real(); - devices.real({ platform: 'android', deviceType: 'phone', version: [4, 4, 2], android: true }); - }, - afterEach: function() { - devices.real(this._savedDevice); - }, -}, () => { - QUnit.test('DateBox should not render dropDownButton in Mozilla on android(T1197922)', function(assert) { - const dateValue = new Date(2016, 6, 15, 14, 30); - const isMozilla = browser.mozilla; - const $dateBox = $('#dateBox').dxDateBox({ - pickerType: 'native', - value: dateValue - }); - - const $dropDownButton = $dateBox.find(`.${DROP_DOWN_BUTTON_CLASS}`); - - assert.strictEqual($dropDownButton.length, isMozilla ? 0 : 1, `dropDownButton is ${isMozilla ? 'not' : ''} rendered`); - }); -}); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownOptions.part1.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownOptions.part1.tests.js new file mode 100644 index 000000000000..28f91789d954 --- /dev/null +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownOptions.part1.tests.js @@ -0,0 +1,15 @@ +import $ from 'jquery'; +import { dropDownEditorsList } from '../../helpers/widgetsList.js'; + +import { widgetTestModule, WIDGET_AMOUNT_PER_FILE } from './dropDownParts/dropDownOptions.tests.js'; + +const dropDownEditorsNames = Object.keys(dropDownEditorsList); + +QUnit.testStart(function() { + const markup = '
\ +
'; + + $('#qunit-fixture').html(markup); +}); + +dropDownEditorsNames.slice(0, WIDGET_AMOUNT_PER_FILE).forEach(widgetTestModule); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownOptions.part2.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownOptions.part2.tests.js new file mode 100644 index 000000000000..f7a632e78d2d --- /dev/null +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownOptions.part2.tests.js @@ -0,0 +1,15 @@ +import $ from 'jquery'; +import { dropDownEditorsList } from '../../helpers/widgetsList.js'; + +import { widgetTestModule, WIDGET_AMOUNT_PER_FILE } from './dropDownParts/dropDownOptions.tests.js'; + +const dropDownEditorsNames = Object.keys(dropDownEditorsList); + +QUnit.testStart(function() { + const markup = '
\ +
'; + + $('#qunit-fixture').html(markup); +}); + +dropDownEditorsNames.slice(WIDGET_AMOUNT_PER_FILE, WIDGET_AMOUNT_PER_FILE * 2).forEach(widgetTestModule); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownOptions.part3.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownOptions.part3.tests.js new file mode 100644 index 000000000000..7e9d49dbe7bc --- /dev/null +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownOptions.part3.tests.js @@ -0,0 +1,15 @@ +import $ from 'jquery'; +import { dropDownEditorsList } from '../../helpers/widgetsList.js'; + +import { widgetTestModule, WIDGET_AMOUNT_PER_FILE } from './dropDownParts/dropDownOptions.tests.js'; + +const dropDownEditorsNames = Object.keys(dropDownEditorsList); + +QUnit.testStart(function() { + const markup = '
\ +
'; + + $('#qunit-fixture').html(markup); +}); + +dropDownEditorsNames.slice(WIDGET_AMOUNT_PER_FILE * 2, WIDGET_AMOUNT_PER_FILE * 3).forEach(widgetTestModule); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownOptions.part4.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownOptions.part4.tests.js new file mode 100644 index 000000000000..83de0f73553f --- /dev/null +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownOptions.part4.tests.js @@ -0,0 +1,15 @@ +import $ from 'jquery'; +import { dropDownEditorsList } from '../../helpers/widgetsList.js'; + +import { widgetTestModule, WIDGET_AMOUNT_PER_FILE } from './dropDownParts/dropDownOptions.tests.js'; + +const dropDownEditorsNames = Object.keys(dropDownEditorsList); + +QUnit.testStart(function() { + const markup = '
\ +
'; + + $('#qunit-fixture').html(markup); +}); + +dropDownEditorsNames.slice(WIDGET_AMOUNT_PER_FILE * 3, WIDGET_AMOUNT_PER_FILE * 4).forEach(widgetTestModule); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownOptions.part5.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownOptions.part5.tests.js new file mode 100644 index 000000000000..17c44314ef3a --- /dev/null +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownOptions.part5.tests.js @@ -0,0 +1,19 @@ +import $ from 'jquery'; +import { dropDownEditorsList } from '../../helpers/widgetsList.js'; + +import { widgetTestModule, WIDGET_AMOUNT_PER_FILE } from './dropDownParts/dropDownOptions.tests.js'; + +const dropDownEditorsNames = Object.keys(dropDownEditorsList); + +QUnit.testStart(function() { + const markup = '
\ +
'; + + $('#qunit-fixture').html(markup); +}); + +dropDownEditorsNames.slice(WIDGET_AMOUNT_PER_FILE * 4).forEach(widgetTestModule); + +QUnit.test('editors splitted by WIDGET_AMOUNT_PER_FILE=2', function(assert) { + assert.strictEqual(dropDownEditorsNames.length <= WIDGET_AMOUNT_PER_FILE * 5, true, 'amount of tested editors is correct, if not -- move extras to separate file'); +}); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownOptions.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownParts/dropDownOptions.tests.js similarity index 97% rename from packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownOptions.tests.js rename to packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownParts/dropDownOptions.tests.js index 9afed79ae994..25c848514574 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownOptions.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownParts/dropDownOptions.tests.js @@ -1,11 +1,11 @@ import $ from 'jquery'; + import fx from 'common/core/animation/fx'; -import { dropDownEditorsList } from '../../helpers/widgetsList.js'; -import { defaultDropDownOptions } from '../../helpers/dropDownOptions.js'; +import { dropDownEditorsList } from '../../../helpers/widgetsList.js'; +import { defaultDropDownOptions } from '../../../helpers/dropDownOptions.js'; import 'fluent_blue_light.css!'; -const dropDownEditorsNames = Object.keys(dropDownEditorsList); const dropDownOptionsKeys = Object.keys(defaultDropDownOptions); const optionTestValues = { @@ -80,13 +80,6 @@ const skipTesting = (assert) => { assert.ok(true, 'tests for this option are implemented separately'); }; -QUnit.testStart(function() { - const markup = '
\ -
'; - - $('#qunit-fixture').html(markup); -}); - const optionComparer = { position: function(assert, editor) { const expectedPosition = { @@ -140,7 +133,7 @@ const optionComparer = { visible: skipTesting }; -dropDownEditorsNames.forEach(widgetName => { +export const widgetTestModule = widgetName => { QUnit.module(widgetName, { beforeEach: function() { fx.off = true; @@ -415,4 +408,6 @@ dropDownEditorsNames.forEach(widgetName => { }); }); }); -}); +}; + +export const WIDGET_AMOUNT_PER_FILE = 2;