From cbe38998776776ed1cadef1497342e482d9e1417 Mon Sep 17 00:00:00 2001 From: Sergio Bur Date: Wed, 29 Apr 2026 17:23:00 +0200 Subject: [PATCH 1/4] 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 ce529c625c80..789ede0816ae 100644 --- a/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts +++ b/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts @@ -1220,7 +1220,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); @@ -1232,7 +1232,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 ef938ae7bc678ab6ba2ca25c7fc58b7e7b58425b Mon Sep 17 00:00:00 2001 From: Sergio Bur Date: Wed, 29 Apr 2026 17:32:42 +0200 Subject: [PATCH 2/4] 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 789ede0816ae..799fc91bf24d 100644 --- a/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts +++ b/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts @@ -1220,7 +1220,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 c73058430f90baced00ad053e1c7a0cbba12bd0c Mon Sep 17 00:00:00 2001 From: Sergio Bur Date: Tue, 5 May 2026 16:14:57 +0200 Subject: [PATCH 3/4] test: 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 9b79e31dc4b3..395f8840b0b1 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 @@ -86,53 +86,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); - }); }); From 3e3c7ce3b3b3ca2e9e6ef147d2591e00ac80f263 Mon Sep 17 00:00:00 2001 From: Sergio Bur Date: Tue, 5 May 2026 16:30:04 +0200 Subject: [PATCH 4/4] fix: fix test --- .../__tests__/selection_end_event.test.ts | 40 +------------------ 1 file changed, 1 insertion(+), 39 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 395f8840b0b1..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 @@ -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'; @@ -29,43 +28,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;