Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {
describe, expect, it, jest,
} from '@jest/globals';
import { logger } from '@ts/core/utils/m_console';

import { createScheduler } from './__mock__/create_scheduler';
import { setupSchedulerTestEnvironment } from './__mock__/m_mock_scheduler';

describe('Scheduler scrollTo deprecation', () => {
it('should log deprecation warning when using old scrollTo API', async () => {
setupSchedulerTestEnvironment();
const loggerWarnSpy = jest.spyOn(logger, 'warn');

const { scheduler } = await createScheduler({
dataSource: [{
text: 'Meeting',
startDate: new Date(2025, 0, 15, 9, 0),
endDate: new Date(2025, 0, 15, 10, 0),
}],
views: ['week'],
currentView: 'week',
currentDate: new Date(2025, 0, 15),
startDayHour: 8,
endDayHour: 18,
});
loggerWarnSpy.mockReset();

const testDate = new Date(2025, 0, 16, 14, 0);

scheduler.scrollTo(testDate, undefined, false);

expect(loggerWarnSpy).toHaveBeenCalledTimes(1);
expect(loggerWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('W0002'),
);
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
describe, expect, it, jest,
beforeAll, beforeEach, describe, expect, it, jest,
} from '@jest/globals';
import { logger } from '@ts/core/utils/m_console';

import { getResourceManagerMock } from '../__mock__/resource_manager.mock';
import SchedulerTimelineDay from '../workspaces/m_timeline_day';
Expand All @@ -12,13 +13,38 @@ import SchedulerWorkSpaceDay from '../workspaces/m_work_space_day';
import SchedulerWorkSpaceMonth from '../workspaces/m_work_space_month';
import SchedulerWorkSpaceWeek from '../workspaces/m_work_space_week';
import SchedulerWorkSpaceWorkWeek from '../workspaces/m_work_space_work_week';
import { setupSchedulerTestEnvironment } from './__mock__/m_mock_scheduler';

jest.mock('@ts/core/m_devices', () => {
const originalModule: any = jest.requireActual('@ts/core/m_devices');
const real = jest.fn().mockReturnValue({
platform: 'mac',
mac: true,
deviceType: 'desktop',
});
const current = jest.fn().mockReturnValue({
platform: 'mac',
mac: true,
deviceType: 'desktop',
});

return {
__esModule: true,
default: {
...originalModule.default,
isSimulator: originalModule.default.isSimulator,
real,
current,
},
};
});

type WorkspaceConstructor<T> = new (container: Element, options?: any) => T;

const createWorkspace = <T extends SchedulerWorkSpace>(
WorkSpace: WorkspaceConstructor<T>,
currentView: string,
): T => {
): { workspace: T; container: Element } => {
const container = document.createElement('div');
const workspace = new WorkSpace(container, {
views: [currentView],
Expand All @@ -30,8 +56,9 @@ const createWorkspace = <T extends SchedulerWorkSpace>(
(workspace as any)._isVisible = () => true;
expect(container.classList).toContain('dx-scheduler-work-space');

return workspace;
return { workspace, container };
};

const workSpaces: {
currentView: string;
WorkSpace: WorkspaceConstructor<SchedulerWorkSpace>;
Expand All @@ -46,10 +73,14 @@ const workSpaces: {
{ currentView: 'timelineMonth', WorkSpace: SchedulerTimelineMonth },
];

beforeAll(() => {
setupSchedulerTestEnvironment();
});

describe('scheduler workspace', () => {
workSpaces.forEach(({ currentView, WorkSpace }) => {
it(`should clear cache on dimension change, view: ${currentView}`, () => {
const workspace = createWorkspace(WorkSpace, currentView);
const { workspace } = createWorkspace(WorkSpace, currentView);
jest.spyOn(workspace.cache, 'clear');

workspace.cache.memo('test', () => 'value');
Expand All @@ -59,7 +90,7 @@ describe('scheduler workspace', () => {
});

it(`should clear cache on _cleanView call, view: ${currentView}`, () => {
const workspace = createWorkspace(WorkSpace, currentView);
const { workspace } = createWorkspace(WorkSpace, currentView);
jest.spyOn(workspace.cache, 'clear');

workspace.cache.memo('test', () => 'value');
Expand All @@ -70,3 +101,95 @@ describe('scheduler workspace', () => {
});
});
});

describe('scheduler workspace scrollTo', () => {
beforeEach(() => {
setupSchedulerTestEnvironment();
});

it('should change scroll position with center alignment', () => {
const { workspace, container } = createWorkspace(SchedulerTimelineDay, 'timelineDay');

const scrollableElement = container.querySelector('.dx-scheduler-date-table-scrollable') as HTMLElement;
const scrollableContainer = scrollableElement.querySelector('.dx-scrollable-container') as HTMLElement;

workspace.scrollTo(new Date(2017, 4, 25, 22, 0));

expect(scrollableContainer.scrollLeft).toBeCloseTo(11125);
});

it('should not change scroll position when date is outside view range', () => {
const { workspace, container } = createWorkspace(SchedulerTimelineDay, 'timelineDay');

const scrollableElement = container.querySelector('.dx-scheduler-date-table-scrollable') as HTMLElement;
const scrollableContainer = scrollableElement.querySelector('.dx-scrollable-container') as HTMLElement;

workspace.scrollTo(new Date(2030, 0, 1));

expect(scrollableContainer.scrollLeft).toBeCloseTo(0);
expect(scrollableContainer.scrollTop).toBeCloseTo(0);
});

it('should scroll with start alignment', () => {
const { workspace, container } = createWorkspace(SchedulerTimelineDay, 'timelineDay');

const scrollableElement = container.querySelector('.dx-scheduler-date-table-scrollable') as HTMLElement;
const scrollableContainer = scrollableElement.querySelector('.dx-scrollable-container') as HTMLElement;

workspace.scrollTo(new Date(2017, 4, 25, 22, 0), undefined, false, true, 'start');

expect(scrollableContainer.scrollLeft).toBeCloseTo(11000);
expect(scrollableContainer.scrollTop).toBeCloseTo(0);
});

it('should scroll with center alignment', () => {
const { workspace, container } = createWorkspace(SchedulerTimelineDay, 'timelineDay');

const scrollableElement = container.querySelector('.dx-scheduler-date-table-scrollable') as HTMLElement;
const scrollableContainer = scrollableElement.querySelector('.dx-scrollable-container') as HTMLElement;

workspace.scrollTo(new Date(2017, 4, 25, 22, 0), undefined, false, true, 'center');

expect(scrollableContainer.scrollLeft).toBeCloseTo(11125);
});

it('should scroll to all day panel when allDay is true', () => {
const { workspace, container } = createWorkspace(SchedulerTimelineDay, 'timelineDay');

const scrollableElement = container.querySelector('.dx-scheduler-date-table-scrollable') as HTMLElement;
const scrollableContainer = scrollableElement.querySelector('.dx-scrollable-container') as HTMLElement;

workspace.scrollTo(new Date(2017, 4, 25, 22, 0), undefined, true);

expect(scrollableContainer.scrollLeft).toBeCloseTo(11125);
});

it('should handle throwWarning parameter correctly', () => {
const loggerWarnSpy = jest.spyOn(logger, 'warn');
loggerWarnSpy.mockReset();

const { workspace, container } = createWorkspace(SchedulerTimelineDay, 'timelineDay');

const scrollableElement = container.querySelector('.dx-scheduler-date-table-scrollable') as HTMLElement;
const scrollableContainer = scrollableElement.querySelector('.dx-scrollable-container') as HTMLElement;

workspace.scrollTo(new Date(2030, 0, 1), undefined, false, true);

expect(scrollableContainer.scrollLeft).toBe(0);
expect(scrollableContainer.scrollTop).toBe(0);
expect(loggerWarnSpy).toHaveBeenCalledTimes(1);
expect(loggerWarnSpy).toHaveBeenCalledWith(expect.stringContaining('W1008'));
});

it('should apply RTL offset when rtlEnabled is true', () => {
const { workspace, container } = createWorkspace(SchedulerTimelineDay, 'timelineDay');
workspace.option('rtlEnabled', true);

const scrollableElement = container.querySelector('.dx-scheduler-date-table-scrollable') as HTMLElement;
const scrollableContainer = scrollableElement.querySelector('.dx-scrollable-container') as HTMLElement;

workspace.scrollTo(new Date(2017, 4, 25, 22, 0));

expect(scrollableContainer.scrollLeft).toBeCloseTo(-11125);
});
});
29 changes: 26 additions & 3 deletions packages/devextreme/js/__internal/scheduler/m_scheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ import { MobileTooltipStrategy } from './tooltip_strategies/m_mobile_tooltip_str
import type {
AppointmentTooltipItem,
SafeAppointment,
TargetedAppointment,
ScrollToGroupValuesOrOptions, ScrollToOptions, TargetedAppointment,
} from './types';
import { AppointmentAdapter } from './utils/appointment_adapter/appointment_adapter';
import { AppointmentDataAccessor } from './utils/data_accessor/appointment_data_accessor';
Expand Down Expand Up @@ -2022,8 +2022,31 @@ class Scheduler extends SchedulerOptionsBaseWidget {
this._appointmentTooltip?.hide();
}

scrollTo(date, groupValues, allDay) {
this._workSpace.scrollTo(date, groupValues, allDay);
scrollTo(
date: Date,
groupValuesOrOptions?: ScrollToGroupValuesOrOptions,
allDay?: boolean | undefined,
) {
let groupValues;
let allDayValue;
let align: 'start' | 'center' = 'center';

if (this._isScrollOptionsObject(groupValuesOrOptions)) {
groupValues = groupValuesOrOptions.group;
allDayValue = groupValuesOrOptions.allDay;
align = groupValuesOrOptions.alignInView ?? 'center';
} else {
errors.log('W0002', 'dxScheduler', 'scrollTo', '26.1', 'Use an object with "group", "allDay" and "alignInView" properties instead of separate parameters.');
groupValues = groupValuesOrOptions;
allDayValue = allDay;
}

this._workSpace.scrollTo(date, groupValues, allDayValue, true, align);
}

private _isScrollOptionsObject(options?: ScrollToGroupValuesOrOptions): options is ScrollToOptions {
return Boolean(options) && typeof options === 'object'
&& ('align' in options || 'allDay' in options || 'group' in options);
}

_isHorizontalVirtualScrolling() {
Expand Down
12 changes: 12 additions & 0 deletions packages/devextreme/js/__internal/scheduler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { dxElementWrapper } from '@js/core/renderer';
import type { Appointment, Properties } from '@js/ui/scheduler';

import type { ResourceLoader } from './utils/loader/resource_loader';
import type { GroupValues, RawGroupValues } from './utils/resource_manager/types';
import type { AppointmentViewModelPlain } from './view_model/types';

export type Direction = 'vertical' | 'horizontal';
Expand Down Expand Up @@ -269,3 +270,14 @@ export interface CompactAppointmentOptions {
allowDrag: boolean;
isCompact: boolean;
}

export interface ScrollToOptions {
group?: RawGroupValues | GroupValues;
allDay?: boolean | undefined;
alignInView?: 'start' | 'center';
}

export type ScrollToGroupValuesOrOptions = RawGroupValues
| GroupValues
| ScrollToOptions
| undefined;
Original file line number Diff line number Diff line change
Expand Up @@ -1792,7 +1792,7 @@ class SchedulerWorkSpace extends Widget<WorkspaceOptionsInternal> {
return result;
}

scrollTo(date, groupValues?: RawGroupValues | GroupValues, allDay = false, throwWarning = true) {
scrollTo(date: Date, groupValues?: RawGroupValues | GroupValues, allDay = false, throwWarning = true, align: 'start' | 'center' = 'center') {
if (!this._isValidScrollDate(date, throwWarning)) {
return;
}
Expand All @@ -1819,8 +1819,8 @@ class SchedulerWorkSpace extends Widget<WorkspaceOptionsInternal> {
const scrollableWidth = getWidth($scrollable);
const cellHeight = this.getCellHeight();

const xShift = (scrollableWidth - cellWidth) / 2;
const yShift = (scrollableHeight - cellHeight) / 2;
const xShift = align === 'start' ? 0 : (scrollableWidth - cellWidth) / 2;
const yShift = align === 'start' ? 0 : (scrollableHeight - cellHeight) / 2;

const left = coordinates.left - scrollable.scrollLeft() - xShift - offset;
let top = coordinates.top - scrollable.scrollTop() - yShift;
Expand Down
16 changes: 16 additions & 0 deletions packages/devextreme/js/ui/scheduler.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ export type AppointmentFormProperties = FormProperties & {
/** @public */
export type ViewType = 'agenda' | 'day' | 'month' | 'timelineDay' | 'timelineMonth' | 'timelineWeek' | 'timelineWorkWeek' | 'week' | 'workWeek';
/** @public */
export type SchedulerScrollToAlign = 'start' | 'center';
/** @public */
export type SchedulerPredefinedToolbarItem = 'today' | 'dateNavigator' | 'viewSwitcher';
/** @public */
export type SchedulerPredefinedDateNavigatorItem = 'prev' | 'next' | 'dateInterval';
Expand Down Expand Up @@ -1308,6 +1310,20 @@ export default class dxScheduler extends Widget<dxSchedulerOptions> {
* @public
*/
scrollTo(date: Date, group?: object, allDay?: boolean): void;
/**
* @docid
* @publicName scrollTo(date, options)
* @param2 options:Object|undefined
* @param2_field group:Object|undefined
* @param2_field allDay:Boolean|undefined
* @param2_field alignInView:Enums.SchedulerScrollToAlign|undefined
* @public
*/
scrollTo(date: Date, options?: {
group?: object;
allDay?: boolean;
alignInView?: SchedulerScrollToAlign;
}): void;
/**
* @docid
* @publicName showAppointmentPopup(appointmentData, createNewAppointment, currentAppointmentData)
Expand Down
1 change: 1 addition & 0 deletions packages/devextreme/js/ui/scheduler_types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export {
AppointmentFormIconsShowMode,
AppointmentFormProperties,
ViewType,
SchedulerScrollToAlign,
SchedulerPredefinedToolbarItem,
SchedulerPredefinedDateNavigatorItem,
AppointmentAddedEvent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,16 @@ module('ScrollTo', {
scheduler.instance.scrollTo(new Date(2020, 8, 5));
await waitAsync(0);

assert.equal(errors.log.callCount, 1, 'warning has been called once');
assert.equal(errors.log.getCall(0).args[0], 'W1008', 'warning has correct error id');
assert.equal(errors.log.callCount, 2, 'warnings have been called twice');
assert.equal(errors.log.getCall(0).args[0], 'W0002', 'first warning is deprecation warning');
assert.equal(errors.log.getCall(1).args[0], 'W1008', 'second warning has correct error id');

scheduler.instance.scrollTo(new Date(2020, 8, 14));
await waitAsync(0);

assert.equal(errors.log.callCount, 2, 'warning has been called once');
assert.equal(errors.log.getCall(1).args[0], 'W1008', 'warning has correct error id');
assert.equal(errors.log.callCount, 4, 'warnings have been called four times total');
assert.equal(errors.log.getCall(2).args[0], 'W0002', 'third warning is deprecation warning');
assert.equal(errors.log.getCall(3).args[0], 'W1008', 'fourth warning has correct error id');
});

test(`A warning should not be thrown when scrolling to a valid date when ${scrolling.text} is used`, async function(assert) {
Expand All @@ -122,7 +124,8 @@ module('ScrollTo', {
scheduler.instance.scrollTo(new Date(2020, 8, 7));
await waitAsync(0);

assert.equal(errors.log.callCount, 0, 'warning has been called once');
assert.equal(errors.log.callCount, 1, 'deprecation warning has been called once');
assert.equal(errors.log.getCall(0).args[0], 'W0002', 'warning is deprecation warning for old API');
});

[{
Expand Down
12 changes: 12 additions & 0 deletions packages/devextreme/ts/dx.all.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25877,6 +25877,17 @@ declare module DevExpress.ui {
* [descr:dxScheduler.scrollTo(date, group, allDay)]
*/
scrollTo(date: Date, group?: object, allDay?: boolean): void;
/**
* [descr:dxScheduler.scrollTo(date, options)]
*/
scrollTo(
date: Date,
options?: {
group?: object;
allDay?: boolean;
alignInView?: DevExpress.ui.dxScheduler.SchedulerScrollToAlign;
}
): void;
/**
* [descr:dxScheduler.showAppointmentPopup(appointmentData, createNewAppointment, currentAppointmentData)]
*/
Expand Down Expand Up @@ -26239,6 +26250,7 @@ declare module DevExpress.ui {
| 'today'
| 'dateNavigator'
| 'viewSwitcher';
export type SchedulerScrollToAlign = 'start' | 'center';
/**
* [descr:TargetedAppointmentInfo]
* @deprecated Attention! This type is for internal purposes only. If you used it previously, please submit a ticket to our {@link https://supportcenter.devexpress.com/ticket/create Support Center}. We will check if there is an alternative solution.
Expand Down
Loading