From 82907d8d4ed98275905f04149316b71e082c32a3 Mon Sep 17 00:00:00 2001 From: Sergio Bur Date: Wed, 29 Apr 2026 17:23:00 +0200 Subject: [PATCH 1/5] feat: new way of fix --- .../__tests__/selection_end_event.test.ts | 138 ++++++++++++++++++ .../scheduler/workspaces/m_work_space.ts | 4 +- .../workSpace.navigation.tests.js | 26 ---- 3 files changed, 140 insertions(+), 28 deletions(-) create mode 100644 packages/devextreme/js/__internal/scheduler/__tests__/selection_end_event.test.ts diff --git a/packages/devextreme/js/__internal/scheduler/__tests__/selection_end_event.test.ts b/packages/devextreme/js/__internal/scheduler/__tests__/selection_end_event.test.ts new file mode 100644 index 000000000000..9b79e31dc4b3 --- /dev/null +++ b/packages/devextreme/js/__internal/scheduler/__tests__/selection_end_event.test.ts @@ -0,0 +1,138 @@ +import { + afterEach, beforeEach, describe, expect, it, jest, +} from '@jest/globals'; +import type { SelectionEndEvent } from '@js/ui/scheduler'; +import { fireEvent } from '@testing-library/dom'; +import support from '@ts/core/utils/m_support'; + +import fx from '../../../common/core/animation/fx'; +import { createScheduler } from './__mock__/create_scheduler'; +import { setupSchedulerTestEnvironment } from './__mock__/m_mock_scheduler'; + +const defaultOptions = { + currentView: 'week', + views: ['week'], + currentDate: new Date(2024, 0, 1), + startDayHour: 9, + endDayHour: 16, + height: 600, +}; + +describe('onSelectionEnd', () => { + beforeEach(() => { + fx.off = true; + setupSchedulerTestEnvironment({ height: 600 }); + }); + + afterEach(() => { + fx.off = false; + document.body.innerHTML = ''; + }); + + it('should fire with selectedCellData on multi-cell mouse drag', async () => { + const onSelectionEnd = jest.fn<(e: SelectionEndEvent) => void>(); + + const { POM, scheduler } = await createScheduler({ + ...defaultOptions, + onSelectionEnd, + }); + + const firstCell = POM.getDateTableCell(0, 0); + const secondCell = POM.getDateTableCell(1, 0); + const thirdCell = POM.getDateTableCell(2, 0); + + fireEvent.mouseDown(firstCell, { which: 1 }); + fireEvent.mouseMove(secondCell); + fireEvent.mouseMove(thirdCell); + fireEvent.mouseUp(thirdCell); + + expect(onSelectionEnd).toHaveBeenCalledTimes(1); + + const { selectedCellData, component } = onSelectionEnd.mock.calls[0][0]; + + expect(selectedCellData).toHaveLength(3); + + const firstStart = selectedCellData[0].startDate; + expect(firstStart.getHours()).toBe(9); + expect(firstStart.getMinutes()).toBe(0); + + expect(selectedCellData[0].endDate.getTime() - firstStart.getTime()).toBe(30 * 60 * 1000); + expect(selectedCellData[1].startDate.getTime()).toBe(selectedCellData[0].endDate.getTime()); + expect(selectedCellData[2].startDate.getTime()).toBe(selectedCellData[1].endDate.getTime()); + + expect(selectedCellData[2].endDate.getHours()).toBe(10); + expect(selectedCellData[2].endDate.getMinutes()).toBe(30); + + expect(component).toBe(scheduler); + }); + + it('T1187849: should select cells with mouse on touch monitor', async () => { + const originalSupportTouch = support.touch; + support.touch = true; + + const { POM, scheduler } = await createScheduler(defaultOptions); + const firstCell = POM.getDateTableCell(0, 0); + const secondCell = POM.getDateTableCell(1, 0); + + expect(scheduler.getWorkSpace().getScrollable().option('scrollByContent')).toBe(true); + + fireEvent.mouseDown(firstCell, { which: 1 }); + fireEvent.mouseMove(secondCell, { which: 1 }); + fireEvent.mouseUp(secondCell, { which: 1 }); + + expect(scheduler.option('selectedCellData')).toHaveLength(2); + expect(firstCell.classList.contains('dx-state-focused')).toBe(true); + expect(secondCell.classList.contains('dx-state-focused')).toBe(true); + + support.touch = originalSupportTouch; + }); + + it('should not fire onSelectionEnd when clicking on an already-selected cell', async () => { + const onSelectionEnd = jest.fn<(e: SelectionEndEvent) => void>(); + + const { POM } = await createScheduler({ + ...defaultOptions, + onSelectionEnd, + }); + + const firstCell = POM.getDateTableCell(0, 0); + const secondCell = POM.getDateTableCell(1, 0); + const thirdCell = POM.getDateTableCell(2, 0); + + fireEvent.mouseDown(firstCell, { which: 1 }); + fireEvent.mouseMove(secondCell); + fireEvent.mouseMove(thirdCell); + fireEvent.mouseUp(thirdCell); + + expect(onSelectionEnd).toHaveBeenCalledTimes(1); + + fireEvent.mouseDown(thirdCell, { which: 1 }); + fireEvent.mouseUp(thirdCell); + + expect(onSelectionEnd).toHaveBeenCalledTimes(1); + }); + + it('should fire onSelectionEnd independently for each Scheduler instance on the page', async () => { + const onSelectionEndA = jest.fn<(e: SelectionEndEvent) => void>(); + const onSelectionEndB = jest.fn<(e: SelectionEndEvent) => void>(); + + const { POM: POMA } = await createScheduler({ + ...defaultOptions, + onSelectionEnd: onSelectionEndA, + }); + await createScheduler({ + ...defaultOptions, + onSelectionEnd: onSelectionEndB, + }); + + const firstCell = POMA.getDateTableCell(0, 0); + const secondCell = POMA.getDateTableCell(1, 0); + + fireEvent.mouseDown(firstCell, { which: 1 }); + fireEvent.mouseMove(secondCell); + fireEvent.mouseUp(secondCell); + + expect(onSelectionEndA).toHaveBeenCalledTimes(1); + expect(onSelectionEndB).toHaveBeenCalledTimes(0); + }); +}); diff --git a/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts b/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts index 537381ca65a4..c1aa933c0954 100644 --- a/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts +++ b/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts @@ -1217,7 +1217,7 @@ class SchedulerWorkSpace extends Widget { (eventsEngine.off as any)(element, SCHEDULER_CELL_DXPOINTERDOWN_EVENT_NAME); eventsEngine.on(element, SCHEDULER_CELL_DXPOINTERDOWN_EVENT_NAME, DRAG_AND_DROP_SELECTOR, (e) => { - if (isMouseEvent(e) && e.which === 1) { + if ((isMouseEvent(e) || isMouseEvent(e.originalEvent)) && e.which === 1) { isPointerDown = true; (this.$element() as any).addClass(WORKSPACE_WITH_MOUSE_SELECTION_CLASS); (eventsEngine.off as any)(domAdapter.getDocument(), SCHEDULER_CELL_DXPOINTERUP_EVENT_NAME); @@ -1229,7 +1229,7 @@ class SchedulerWorkSpace extends Widget { }); eventsEngine.on(element, SCHEDULER_CELL_DXPOINTERMOVE_EVENT_NAME, DRAG_AND_DROP_SELECTOR, (e) => { - if (isPointerDown && this._dateTableScrollable && !this._dateTableScrollable.option('scrollByContent')) { + if (isPointerDown && this._dateTableScrollable) { e.preventDefault(); e.stopPropagation(); this.moveToCell($(e.target), true); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/workSpace.navigation.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/workSpace.navigation.tests.js index c11bba1ae870..e81889df6bfd 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/workSpace.navigation.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/workSpace.navigation.tests.js @@ -779,32 +779,6 @@ module('Workspace navigation', () => { assert.equal(cells.filter('.dx-state-focused').length, 1, 'right quantity of focused cells'); }); - test('It should not be possible to select cells via mouse if scrollable \'scrollByContent\' is true', async function(assert) { - const $element = this.createInstance({ - focusStateEnabled: true, - firstDayOfWeek: 1, - currentDate: new Date(2015, 3, 1), - height: 400, - allowMultipleCellSelection: true, - onContentReady: function(e) { - const scrollable = e.component._dateTableScrollable; - scrollable.option('scrollByContent', true); - }, - }, 'dxSchedulerWorkSpaceMonth'); - const workspace = $element.dxSchedulerWorkSpaceMonth('instance'); - - const stub = sinon.stub(workspace, 'notifyObserver'); - - const cells = $element.find('.' + CELL_CLASS); - const cell = cells.eq(23).get(0); - const $table = $element.find('.dx-scheduler-date-table'); - - pointerMock(cells.eq(2)).start().click(); - $($table).trigger($.Event('dxpointermove', { target: cell, toElement: cell, which: 1 })); - - assert.notOk(stub.calledOnce, 'Cells weren\'t selected'); - }); - test('Multiselection with left arrow should work in workspace day', async function(assert) { const $element = this.createInstance({ focusStateEnabled: true, From bb1dbb6475e87f5de8110da797a735c859591ec2 Mon Sep 17 00:00:00 2001 From: Sergio Bur Date: Wed, 29 Apr 2026 17:32:42 +0200 Subject: [PATCH 2/5] fix: fix tests --- .../js/__internal/scheduler/workspaces/m_work_space.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts b/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts index c1aa933c0954..c8d31cfd2b9f 100644 --- a/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts +++ b/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts @@ -1217,7 +1217,7 @@ class SchedulerWorkSpace extends Widget { (eventsEngine.off as any)(element, SCHEDULER_CELL_DXPOINTERDOWN_EVENT_NAME); eventsEngine.on(element, SCHEDULER_CELL_DXPOINTERDOWN_EVENT_NAME, DRAG_AND_DROP_SELECTOR, (e) => { - if ((isMouseEvent(e) || isMouseEvent(e.originalEvent)) && e.which === 1) { + if ((isMouseEvent(e) || (e.originalEvent && isMouseEvent(e.originalEvent))) && e.which === 1) { isPointerDown = true; (this.$element() as any).addClass(WORKSPACE_WITH_MOUSE_SELECTION_CLASS); (eventsEngine.off as any)(domAdapter.getDocument(), SCHEDULER_CELL_DXPOINTERUP_EVENT_NAME); From 0a3b998246490568deadb27448837f6491f96886 Mon Sep 17 00:00:00 2001 From: Sergio Bur Date: Tue, 5 May 2026 16:31:42 +0200 Subject: [PATCH 3/5] fix: fix test --- .../__tests__/__mock__/model/scheduler.ts | 14 +++++++ .../__tests__/selection_end_event.test.ts | 37 ------------------- 2 files changed, 14 insertions(+), 37 deletions(-) diff --git a/packages/devextreme/js/__internal/scheduler/__tests__/__mock__/model/scheduler.ts b/packages/devextreme/js/__internal/scheduler/__tests__/__mock__/model/scheduler.ts index 675538c324d9..8a69a68ac8f6 100644 --- a/packages/devextreme/js/__internal/scheduler/__tests__/__mock__/model/scheduler.ts +++ b/packages/devextreme/js/__internal/scheduler/__tests__/__mock__/model/scheduler.ts @@ -76,6 +76,20 @@ export class SchedulerModel { return getTexts(cells); } + getDateTableCell(rowIndex = 0, cellIndex = 0): HTMLElement { + const rowSelector = `.dx-scheduler-date-table-row:nth-child(${rowIndex + 1})`; + const cellSelector = `.dx-scheduler-date-table-cell:nth-child(${cellIndex + 1})`; + const selector = `${rowSelector} ${cellSelector}`; + + const result = this.container.querySelector(selector); + + if (!result) { + throw new Error(`Date cell in row ${rowIndex} and column ${cellIndex} not found`); + } + + return result as HTMLElement; + } + getHeaderPanelContent(): string[] { const cells = this.container.querySelectorAll('.dx-scheduler-header-panel-cell'); return getTexts(cells); diff --git a/packages/devextreme/js/__internal/scheduler/__tests__/selection_end_event.test.ts b/packages/devextreme/js/__internal/scheduler/__tests__/selection_end_event.test.ts index 9b79e31dc4b3..b621214632ac 100644 --- a/packages/devextreme/js/__internal/scheduler/__tests__/selection_end_event.test.ts +++ b/packages/devextreme/js/__internal/scheduler/__tests__/selection_end_event.test.ts @@ -29,43 +29,6 @@ describe('onSelectionEnd', () => { document.body.innerHTML = ''; }); - it('should fire with selectedCellData on multi-cell mouse drag', async () => { - const onSelectionEnd = jest.fn<(e: SelectionEndEvent) => void>(); - - const { POM, scheduler } = await createScheduler({ - ...defaultOptions, - onSelectionEnd, - }); - - const firstCell = POM.getDateTableCell(0, 0); - const secondCell = POM.getDateTableCell(1, 0); - const thirdCell = POM.getDateTableCell(2, 0); - - fireEvent.mouseDown(firstCell, { which: 1 }); - fireEvent.mouseMove(secondCell); - fireEvent.mouseMove(thirdCell); - fireEvent.mouseUp(thirdCell); - - expect(onSelectionEnd).toHaveBeenCalledTimes(1); - - const { selectedCellData, component } = onSelectionEnd.mock.calls[0][0]; - - expect(selectedCellData).toHaveLength(3); - - const firstStart = selectedCellData[0].startDate; - expect(firstStart.getHours()).toBe(9); - expect(firstStart.getMinutes()).toBe(0); - - expect(selectedCellData[0].endDate.getTime() - firstStart.getTime()).toBe(30 * 60 * 1000); - expect(selectedCellData[1].startDate.getTime()).toBe(selectedCellData[0].endDate.getTime()); - expect(selectedCellData[2].startDate.getTime()).toBe(selectedCellData[1].endDate.getTime()); - - expect(selectedCellData[2].endDate.getHours()).toBe(10); - expect(selectedCellData[2].endDate.getMinutes()).toBe(30); - - expect(component).toBe(scheduler); - }); - it('T1187849: should select cells with mouse on touch monitor', async () => { const originalSupportTouch = support.touch; support.touch = true; From 08f8d3146b4ad1e53be39414403b732c02fe9f4a Mon Sep 17 00:00:00 2001 From: Sergio Bur Date: Tue, 5 May 2026 16:30:04 +0200 Subject: [PATCH 4/5] fix: fix test --- .../__internal/scheduler/__tests__/selection_end_event.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/devextreme/js/__internal/scheduler/__tests__/selection_end_event.test.ts b/packages/devextreme/js/__internal/scheduler/__tests__/selection_end_event.test.ts index b621214632ac..7fdc40237a5b 100644 --- a/packages/devextreme/js/__internal/scheduler/__tests__/selection_end_event.test.ts +++ b/packages/devextreme/js/__internal/scheduler/__tests__/selection_end_event.test.ts @@ -1,7 +1,6 @@ import { - afterEach, beforeEach, describe, expect, it, jest, + afterEach, beforeEach, describe, expect, it, } from '@jest/globals'; -import type { SelectionEndEvent } from '@js/ui/scheduler'; import { fireEvent } from '@testing-library/dom'; import support from '@ts/core/utils/m_support'; From d386f4b440d8a0bcda8d337c269a05ca8fc3d63c Mon Sep 17 00:00:00 2001 From: Sergio Bur Date: Wed, 6 May 2026 11:42:05 +0200 Subject: [PATCH 5/5] fix: fix test --- .../__tests__/selection_end_event.test.ts | 49 ------------------- 1 file changed, 49 deletions(-) diff --git a/packages/devextreme/js/__internal/scheduler/__tests__/selection_end_event.test.ts b/packages/devextreme/js/__internal/scheduler/__tests__/selection_end_event.test.ts index 7fdc40237a5b..9f5258c0f346 100644 --- a/packages/devextreme/js/__internal/scheduler/__tests__/selection_end_event.test.ts +++ b/packages/devextreme/js/__internal/scheduler/__tests__/selection_end_event.test.ts @@ -48,53 +48,4 @@ describe('onSelectionEnd', () => { support.touch = originalSupportTouch; }); - - it('should not fire onSelectionEnd when clicking on an already-selected cell', async () => { - const onSelectionEnd = jest.fn<(e: SelectionEndEvent) => void>(); - - const { POM } = await createScheduler({ - ...defaultOptions, - onSelectionEnd, - }); - - const firstCell = POM.getDateTableCell(0, 0); - const secondCell = POM.getDateTableCell(1, 0); - const thirdCell = POM.getDateTableCell(2, 0); - - fireEvent.mouseDown(firstCell, { which: 1 }); - fireEvent.mouseMove(secondCell); - fireEvent.mouseMove(thirdCell); - fireEvent.mouseUp(thirdCell); - - expect(onSelectionEnd).toHaveBeenCalledTimes(1); - - fireEvent.mouseDown(thirdCell, { which: 1 }); - fireEvent.mouseUp(thirdCell); - - expect(onSelectionEnd).toHaveBeenCalledTimes(1); - }); - - it('should fire onSelectionEnd independently for each Scheduler instance on the page', async () => { - const onSelectionEndA = jest.fn<(e: SelectionEndEvent) => void>(); - const onSelectionEndB = jest.fn<(e: SelectionEndEvent) => void>(); - - const { POM: POMA } = await createScheduler({ - ...defaultOptions, - onSelectionEnd: onSelectionEndA, - }); - await createScheduler({ - ...defaultOptions, - onSelectionEnd: onSelectionEndB, - }); - - const firstCell = POMA.getDateTableCell(0, 0); - const secondCell = POMA.getDateTableCell(1, 0); - - fireEvent.mouseDown(firstCell, { which: 1 }); - fireEvent.mouseMove(secondCell); - fireEvent.mouseUp(secondCell); - - expect(onSelectionEndA).toHaveBeenCalledTimes(1); - expect(onSelectionEndB).toHaveBeenCalledTimes(0); - }); });