-
Notifications
You must be signed in to change notification settings - Fork 663
Scheduler: T1310524 — shadeUntilCurrentTime property is inconsistently applied to resources outside the viewport in virtual scrolling mode #32125
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 26_1
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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' }, | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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); | ||
|
||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1579,13 +1579,24 @@ class SchedulerWorkSpace extends Widget<WorkspaceOptionsInternal> { | |||||||||||||||||||||||||||||||||||
| 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++; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1582
to
+1589
|
||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| return width / (totalCellCount + cellCount - startIndex); | ||||||||||||||||||||||||||||||||||||
| const divisor = totalCellCount + cellCount - startIndex; | ||||||||||||||||||||||||||||||||||||
| let result = width / divisor; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
| // When virtual scrolling is enabled, cells for some groups may not be rendered in the DOM. | |
| // In that case, the computed width can be 0 with no elements found for the target group. | |
| // To keep features such as shadeUntilCurrentTime working correctly, fall back to using | |
| // group 0's cell width as a reference for shader rendering. |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The recursive fallback to group 0 may still return 0 if group 0 is also not rendered (e.g., in extreme virtual scrolling scenarios where even group 0 is not in the viewport). Consider adding a fallback to use getCellWidth() or a default minimum cell width when group 0 also fails to provide a valid width.
| let result = width / divisor; | |
| if ((result === 0 || foundElements < cellCount) && groupIndex > 0) { | |
| result = this.getRoundedCellWidth(0, 0, cellCount); | |
| } | |
| let result = divisor > 0 ? width / divisor : 0; | |
| if ((result === 0 || !isFinite(result) || foundElements < cellCount) && groupIndex > 0) { | |
| result = this.getRoundedCellWidth(0, 0, cellCount); | |
| } | |
| if (result === 0 || !isFinite(result)) { | |
| const fallbackWidth = this.getCellWidth(); | |
| if (fallbackWidth > 0) { | |
| result = fallbackWidth; | |
| } | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The CLASSES object defines 'workSpace' and 'shaderTop' properties that are not used in this test. Consider removing these unused properties or adding additional test assertions that use them to verify the shader rendering behavior.