Skip to content
Open
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,73 @@
import {
beforeEach, describe, expect, it,
} from '@jest/globals';
import type { Properties } from '@js/ui/scheduler';

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

const APPOINTMENT_SELECTOR = '.dx-scheduler-appointment';
const HANDLE_TOP_SELECTOR = '.dx-resizable-handle-top';
const HANDLE_BOTTOM_SELECTOR = '.dx-resizable-handle-bottom';

const getResizeHandles = (container: HTMLElement): string[][] => Array
.from(container.querySelectorAll(APPOINTMENT_SELECTOR))
.map((part) => [
...(part.querySelector(HANDLE_TOP_SELECTOR) ? ['top'] : []),
...(part.querySelector(HANDLE_BOTTOM_SELECTOR) ? ['bottom'] : []),
]);

const baseConfig: Properties = {
currentDate: new Date(2021, 3, 12),
views: ['week'],
currentView: 'week',
editing: { allowUpdating: true, allowResizing: true },
height: 600,
};

describe('Appointments resizing in vertical views', () => {
beforeEach(() => {
setupSchedulerTestEnvironment();
});

it('should render both resize handles on an appointment that is not split', async () => {
const { container } = await createScheduler({
...baseConfig,
dataSource: [{
text: 'Short',
startDate: new Date(2021, 3, 12, 9),
endDate: new Date(2021, 3, 12, 11),
}],
});

expect(getResizeHandles(container)).toEqual([['top', 'bottom']]);
});

it('should render resize handles only on the edges of an appointment split by midnight', async () => {
const { container } = await createScheduler({
...baseConfig,
dataSource: [{
text: 'Long',
startDate: new Date(2021, 3, 12, 22),
endDate: new Date(2021, 3, 13, 3),
}],
});

expect(getResizeHandles(container)).toEqual([['top'], ['bottom']]);
});

it('should render resize handles only on the edges of an all day appointment when the all day panel is hidden', async () => {
const { container } = await createScheduler({
...baseConfig,
allDayPanelMode: 'hidden',
dataSource: [{
text: 'All day',
allDay: true,
startDate: new Date(2021, 3, 12, 5),
endDate: new Date(2021, 3, 13, 5),
}],
});

expect(getResizeHandles(container)).toEqual([['top'], [], ['bottom']]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,15 @@ export class Appointment extends DOMComponent<AppointmentProperties> {
}

_getVerticalResizingRule() {
const reducedHandles = {
head: 'top',
body: '',
tail: 'bottom',
};
const height = Math.round(this.invoke('getCellHeight'));

return {
handles: DEFAULT_VERTICAL_HANDLES,
handles: this.option('reduced') ? reducedHandles[this.option('reduced') as any] : DEFAULT_VERTICAL_HANDLES,
minWidth: 0,
minHeight: height,
step: height,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { describe, expect, it } from '@jest/globals';
import { mockFieldExpressions } from '@ts/scheduler/__mock__/appointment_data_accessor.mock';

import subscribes from './m_subscribes';
import type { ViewType } from './types';
import { AppointmentDataAccessor } from './utils/data_accessor/appointment_data_accessor';

const CELL_WIDTH = 100;
const CELL_HEIGHT = 50;
const CELL_DURATION = 30;
const HOUR_MS = 3600000;
const DAY_MS = 24 * HOUR_MS;

const createScheduler = (viewType: ViewType, allDayPanelMode = 'all'): unknown => ({
currentView: { type: viewType },
_dataAccessors: new AppointmentDataAccessor(mockFieldExpressions, true, 'yyyy/MM/dd HH:mm:ss'),
option: (name: string): unknown => (name === 'allDayPanelMode' ? allDayPanelMode : undefined),
getWorkSpace: () => ({
getCellWidth: () => CELL_WIDTH,
getCellHeight: () => CELL_HEIGHT,
option: (name: string): unknown => (name === 'cellDuration' ? CELL_DURATION : undefined),
positionHelper: { getResizableStep: () => CELL_WIDTH },
}),
});

const getDeltaTime = (
viewType: ViewType,
allDay: boolean,
size: { width: number; height: number },
allDayPanelMode = 'all',
): number => subscribes.getDeltaTime.call(
createScheduler(viewType, allDayPanelMode),
size,
{ width: 0, height: 0 },
{
startDate: new Date(2021, 3, 12, 9),
endDate: allDay ? new Date(2021, 3, 13, 9) : new Date(2021, 3, 12, 10),
allDay,
},
);

describe('getDeltaTime', () => {
describe('timeline views', () => {
it.each(['timelineDay', 'timelineWeek', 'timelineWorkWeek'] as ViewType[])(
'should resize an all day appointment by the cell duration in %s',
(viewType) => {
expect(getDeltaTime(viewType, true, { width: CELL_WIDTH, height: 0 }))
.toBe(CELL_DURATION * 60000);
},
);

it('should resize a regular appointment by the cell duration', () => {
expect(getDeltaTime('timelineWeek', false, { width: CELL_WIDTH, height: 0 }))
.toBe(CELL_DURATION * 60000);
});

it('should resize an all day appointment by whole days in timelineMonth', () => {
expect(getDeltaTime('timelineMonth', true, { width: CELL_WIDTH, height: 0 }))
.toBe(DAY_MS);
});
});

describe('vertical views', () => {
it('should resize an all day appointment by whole days', () => {
expect(getDeltaTime('week', true, { width: CELL_WIDTH, height: 0 })).toBe(DAY_MS);
});

it('should resize an all day appointment by the cell duration when the all day panel is hidden', () => {
expect(getDeltaTime('week', true, { width: 0, height: CELL_HEIGHT }, 'hidden'))
.toBe(CELL_DURATION * 60000);
});

it('should resize a regular appointment by the cell duration', () => {
expect(getDeltaTime('week', false, { width: 0, height: CELL_HEIGHT }))
.toBe(CELL_DURATION * 60000);
});
});

describe('month view', () => {
it('should resize an all day appointment by whole days', () => {
expect(getDeltaTime('month', true, { width: CELL_WIDTH, height: 0 })).toBe(DAY_MS);
});
});
});
11 changes: 10 additions & 1 deletion packages/devextreme/js/__internal/scheduler/m_subscribes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ const isAllDay = (
return adapter.allDay;
};

// NOTE: Only in vertical views an all day appointment lives in the all day panel
// and is resized by whole days. In timeline views it is rendered in the date table
// and resized by the cell duration; month and timelineMonth are day-based anyway.
const isAllDayPanelAppointment = (
scheduler: Scheduler,
appointmentData: SafeAppointment,
): boolean => VERTICAL_VIEW_TYPES.includes(scheduler.currentView.type)
&& isAllDay(scheduler, appointmentData);

const subscribes = {
isCurrentViewAgenda() {
return this.currentView.type === 'agenda';
Expand Down Expand Up @@ -188,7 +197,7 @@ const subscribes = {
},
cellDurationInMinutes: this.getWorkSpace().option('cellDuration'),
resizableStep: this.getWorkSpace().positionHelper.getResizableStep(),
isAllDayPanel: isAllDay(this, itemData),
isAllDayPanel: isAllDayPanelAppointment(this, itemData),
});
},

Expand Down
Loading