diff --git a/e2e/testcafe-devextreme/tests/scheduler/common/layout/timeIndication/etalons/shader-virtual-scrolling-week-end (fluent.blue.light).png b/e2e/testcafe-devextreme/tests/scheduler/common/layout/timeIndication/etalons/shader-virtual-scrolling-week-end (fluent.blue.light).png new file mode 100644 index 000000000000..0cfac68a6768 Binary files /dev/null and b/e2e/testcafe-devextreme/tests/scheduler/common/layout/timeIndication/etalons/shader-virtual-scrolling-week-end (fluent.blue.light).png differ diff --git a/e2e/testcafe-devextreme/tests/scheduler/common/layout/timeIndication/etalons/shader-virtual-scrolling-week-start (fluent.blue.light).png b/e2e/testcafe-devextreme/tests/scheduler/common/layout/timeIndication/etalons/shader-virtual-scrolling-week-start (fluent.blue.light).png new file mode 100644 index 000000000000..bf4967c361d3 Binary files /dev/null and b/e2e/testcafe-devextreme/tests/scheduler/common/layout/timeIndication/etalons/shader-virtual-scrolling-week-start (fluent.blue.light).png differ diff --git a/e2e/testcafe-devextreme/tests/scheduler/common/layout/timeIndication/shader-virtual-scrolling.ts b/e2e/testcafe-devextreme/tests/scheduler/common/layout/timeIndication/shader-virtual-scrolling.ts new file mode 100644 index 000000000000..1eccf9e7c6fc --- /dev/null +++ b/e2e/testcafe-devextreme/tests/scheduler/common/layout/timeIndication/shader-virtual-scrolling.ts @@ -0,0 +1,72 @@ +import { createScreenshotsComparer } from 'devextreme-screenshot-comparer'; +import Scheduler from 'devextreme-testcafe-models/scheduler'; +import { insertStylesheetRulesToPage } from '../../../../../helpers/domUtils'; +import { createWidget } from '../../../../../helpers/createWidget'; +import url from '../../../../../helpers/getPageUrl'; +import { testScreenshot } from '../../../../../helpers/themeUtils'; + +fixture.disablePageReloads`Scheduler: Current Time Indication: Shader with Virtual Scrolling` + .page(url(__dirname, '../../../../container.html')) + .beforeEach(async (t) => { + await t.resizeWindow(2560, 600); + }); + +const style = ` +.dx-scheduler-date-time-shader-top::before, +.dx-scheduler-date-time-shader-bottom::before, +.dx-scheduler-timeline .dx-scheduler-date-time-shader::before, +.dx-scheduler-date-time-shader-all-day { + background-color: red !important; +}`; + +const resources = [ + { text: 'Room 1', id: 1, color: '#cb6bb2' }, + { text: 'Room 2', id: 2, color: '#56ca85' }, + { text: 'Room 3', id: 3, color: '#1e90ff' }, + { text: 'Room 4', id: 4, color: '#ff9747' }, + { text: 'Room 5', id: 5, color: '#ff6a00' }, + { text: 'Room 6', id: 6, color: '#ffc0cb' }, +]; + +test('Shader should have correct width and left positions when scrolled to last groups with virtual scrolling (T1310524)', async (t) => { + const scheduler = new Scheduler('#container'); + const { takeScreenshot, compareResults } = createScreenshotsComparer(t); + + await scheduler.option('width', 2560); + + await testScreenshot( + t, + takeScreenshot, + 'shader-virtual-scrolling-week-start.png', + { element: scheduler.element }, + ); + + await scheduler.scrollTo(new Date(2025, 9, 15, 17, 30), { roomId: 6 }); + + await testScreenshot( + t, + takeScreenshot, + 'shader-virtual-scrolling-week-end.png', + { element: scheduler.element }, + ); + + await t.expect(compareResults.isValid()) + .ok(compareResults.errorMessages()); +}).before(async () => { + await insertStylesheetRulesToPage(style); + + await createWidget('dxScheduler', { + dataSource: [], + currentView: 'week', + views: ['week'], + groups: ['roomId'], + resources: [{ fieldExpr: 'roomId', dataSource: resources, label: 'Room' }], + startDayHour: 8, + endDayHour: 18, + currentDate: new Date(2025, 9, 15), + height: 400, + width: 800, + shadeUntilCurrentTime: true, + scrolling: { mode: 'virtual' }, + }); +}); diff --git a/packages/devextreme/js/__internal/scheduler/__tests__/workspace.test.ts b/packages/devextreme/js/__internal/scheduler/__tests__/workspace.test.ts new file mode 100644 index 000000000000..2721336bba4f --- /dev/null +++ b/packages/devextreme/js/__internal/scheduler/__tests__/workspace.test.ts @@ -0,0 +1,71 @@ +import { + afterEach, beforeEach, describe, expect, it, +} from '@jest/globals'; +import $ from '@js/core/renderer'; + +import fx from '../../../common/core/animation/fx'; +import { createScheduler } from './__mock__/create_scheduler'; +import { setupSchedulerTestEnvironment } from './__mock__/m_mock_scheduler'; + +const CLASSES = { + scheduler: 'dx-scheduler', + workSpace: 'dx-scheduler-work-space', + shaderTop: 'dx-scheduler-date-time-shader-top', +}; + +describe('Workspace', () => { + beforeEach(() => { + fx.off = true; + setupSchedulerTestEnvironment({ height: 600 }); + }); + + afterEach(() => { + const $scheduler = $(document.querySelector(`.${CLASSES.scheduler}`)); + // @ts-expect-error + $scheduler.dxScheduler('dispose'); + document.body.innerHTML = ''; + fx.off = false; + }); + + it('should use group 0 width as fallback when groups are not rendered due to virtual scrolling (T1310524)', async () => { + const resources = [ + { text: 'Room 1', id: 1, color: '#cb6bb2' }, + { text: 'Room 2', id: 2, color: '#56ca85' }, + { text: 'Room 3', id: 3, color: '#1e90ff' }, + { text: 'Room 4', id: 4, color: '#ff9747' }, + { text: 'Room 5', id: 5, color: '#ff6a00' }, + { text: 'Room 6', id: 6, color: '#ffc0cb' }, + ]; + + const { scheduler } = await createScheduler({ + currentView: 'week', + views: ['week'], + groups: ['roomId'], + resources: [{ fieldExpr: 'roomId', dataSource: resources, label: 'Room' }], + dataSource: [ + { + text: 'Meeting 1', startDate: new Date(2025, 9, 15, 9), endDate: new Date(2025, 9, 15, 10), roomId: 1, + }, + { + text: 'Meeting 2', startDate: new Date(2025, 9, 15, 9), endDate: new Date(2025, 9, 15, 10), roomId: 4, + }, + ], + startDayHour: 8, + endDayHour: 18, + currentDate: new Date(2025, 9, 15), + height: 400, + shadeUntilCurrentTime: true, + scrolling: { mode: 'virtual' }, + }); + + const workSpace = scheduler.getWorkSpace(); + const cellCount = workSpace._getCellCount(); + const lastGroupWidth = workSpace.getRoundedCellWidth( + resources.length - 1, + (resources.length - 1) * cellCount, + cellCount, + ); + + expect(lastGroupWidth).toBeGreaterThan(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 c1649f57b664..a986cde507e0 100644 --- a/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts +++ b/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts @@ -1579,13 +1579,24 @@ class SchedulerWorkSpace extends Widget { startIndex = totalCellCount; } + let foundElements = 0; for (let i = startIndex; i < totalCellCount + cellCount; i++) { const element = $($cells).eq(i).get(0); const elementWidth = element ? getBoundingRect(element).width : 0; width += elementWidth; + if (element) { + foundElements++; + } } - return width / (totalCellCount + cellCount - startIndex); + const divisor = totalCellCount + cellCount - startIndex; + let result = width / divisor; + + if ((result === 0 || foundElements < cellCount) && groupIndex > 0) { + result = this.getRoundedCellWidth(0, 0, cellCount); + } + + return result; } // Mappings