Skip to content

Commit 2aa83c4

Browse files
authored
Scheduler - Appointments Refactoring - Custom Templates (#33158)
1 parent 8b3d895 commit 2aa83c4

File tree

10 files changed

+452
-54
lines changed

10 files changed

+452
-54
lines changed
Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
import {
2+
afterEach, beforeEach, describe, expect, it, jest,
3+
} from '@jest/globals';
4+
import type { dxElementWrapper } from '@js/core/renderer';
5+
import $ from '@js/core/renderer';
6+
import type { Properties } from '@js/ui/scheduler';
7+
8+
import { createScheduler as baseCreateScheduler } from './__mock__/create_scheduler';
9+
import { setupSchedulerTestEnvironment } from './__mock__/m_mock_scheduler';
10+
import type { SchedulerModel } from './__mock__/model/scheduler';
11+
12+
const createScheduler = (config: Properties) => baseCreateScheduler({
13+
...config,
14+
// eslint-disable-next-line @typescript-eslint/naming-convention
15+
_newAppointments: true,
16+
});
17+
18+
describe('New Appointments', () => {
19+
beforeEach(() => {
20+
setupSchedulerTestEnvironment();
21+
});
22+
23+
afterEach(() => {
24+
const $scheduler = $('.dx-scheduler');
25+
// @ts-expect-error
26+
$scheduler.dxScheduler('dispose');
27+
document.body.innerHTML = '';
28+
});
29+
30+
describe('Templates', () => {
31+
describe.each([
32+
'appointmentTemplate',
33+
'appointmentCollectorTemplate',
34+
])('%s common', (templateName) => {
35+
const config = {
36+
dataSource: [
37+
{ text: 'Appointment 1', startDate: new Date(2015, 1, 9, 8), endDate: new Date(2015, 1, 9, 9) },
38+
{ text: 'Appointment 2', startDate: new Date(2015, 1, 9, 8), endDate: new Date(2015, 1, 9, 9) },
39+
{ text: 'Appointment 3', startDate: new Date(2015, 1, 9, 8), endDate: new Date(2015, 1, 9, 9) },
40+
],
41+
maxAppointmentsPerCell: 2,
42+
currentView: 'day',
43+
currentDate: new Date(2015, 1, 9, 8),
44+
};
45+
46+
const appointmentCollectorTemplate = (
47+
data: any,
48+
container: HTMLElement,
49+
): dxElementWrapper => $(container).text('Custom collector');
50+
51+
const appointmentTemplate = (
52+
data: any,
53+
index: number,
54+
container: HTMLElement,
55+
): dxElementWrapper => $(container).text('Custom appointment');
56+
57+
const templateFunction = templateName === 'appointmentCollectorTemplate'
58+
? appointmentCollectorTemplate
59+
: appointmentTemplate;
60+
61+
const isTemplateApplied = (POM: SchedulerModel): boolean => {
62+
if (templateName === 'appointmentCollectorTemplate') {
63+
return $(POM.getCollectorButton()).text() === 'Custom collector';
64+
}
65+
66+
return $(POM.getAppointments()[0].element).text() === 'Custom appointment';
67+
};
68+
69+
it('should apply custom template', async () => {
70+
const { POM } = await createScheduler({
71+
...config,
72+
[templateName]: templateFunction,
73+
});
74+
75+
expect(isTemplateApplied(POM)).toBe(true);
76+
});
77+
78+
it('should apply custom template after .option() change', async () => {
79+
const { POM, scheduler } = await createScheduler(config);
80+
81+
scheduler.option(templateName, templateFunction);
82+
await new Promise(process.nextTick);
83+
84+
expect(isTemplateApplied(POM)).toBe(true);
85+
});
86+
87+
it('should apply default template if current view does not have it', async () => {
88+
const defaultTemplate = jest.fn();
89+
90+
const { POM } = await createScheduler({
91+
...config,
92+
views: [{ type: 'day' }],
93+
[templateName]: defaultTemplate,
94+
});
95+
96+
expect(defaultTemplate).toHaveBeenCalled();
97+
expect(isTemplateApplied(POM)).toBe(false);
98+
});
99+
100+
it('should apply default template after .option() change with default value', async () => {
101+
const defaultValue = templateName === 'appointmentCollectorTemplate'
102+
? 'appointmentCollector'
103+
: 'appointment';
104+
105+
const { POM, scheduler } = await createScheduler({
106+
...config,
107+
[templateName]: templateFunction,
108+
});
109+
110+
scheduler.option(templateName, defaultValue);
111+
await new Promise(process.nextTick);
112+
113+
expect(isTemplateApplied(POM)).toBe(false);
114+
115+
if (templateName === 'appointmentTemplate') {
116+
const appointment = POM.getAppointments()[0];
117+
expect(appointment.getText()).toBe('Appointment 1');
118+
} else {
119+
const collectorButton = POM.getCollectorButton();
120+
expect(collectorButton.textContent).toBe('1');
121+
}
122+
});
123+
124+
it('should apply current view\'s template', async () => {
125+
const defaultTemplate = jest.fn();
126+
127+
const { POM } = await createScheduler({
128+
...config,
129+
views: [{
130+
type: 'day',
131+
[templateName]: templateFunction,
132+
}],
133+
[templateName]: defaultTemplate,
134+
});
135+
136+
expect(defaultTemplate).not.toHaveBeenCalled();
137+
138+
expect(isTemplateApplied(POM)).toBe(true);
139+
});
140+
141+
it('should apply current view\'s template after .option() change', async () => {
142+
const { POM, scheduler } = await createScheduler({
143+
...config,
144+
views: [{
145+
type: 'day',
146+
[templateName]: templateFunction,
147+
}],
148+
});
149+
150+
const defaultTemplate = jest.fn();
151+
152+
scheduler.option(templateName, defaultTemplate);
153+
await new Promise(process.nextTick);
154+
155+
expect(defaultTemplate).not.toHaveBeenCalled();
156+
expect(isTemplateApplied(POM)).toBe(true);
157+
});
158+
159+
it('should apply current view\'s template after current view was changed', async () => {
160+
const defaultTemplate = jest.fn();
161+
162+
const { POM, scheduler } = await createScheduler({
163+
...config,
164+
views: [
165+
{ type: 'workWeek', [templateName]: defaultTemplate },
166+
{ type: 'day', [templateName]: templateFunction },
167+
],
168+
currentView: 'workWeek',
169+
});
170+
171+
defaultTemplate.mockClear();
172+
173+
scheduler.option('currentView', 'day');
174+
await new Promise(process.nextTick);
175+
176+
expect(defaultTemplate).not.toHaveBeenCalled();
177+
expect(isTemplateApplied(POM)).toBe(true);
178+
});
179+
});
180+
181+
describe('AppointmentTemplate', () => {
182+
it('should call template function with correct parameters', async () => {
183+
const appointmentTemplate = jest.fn();
184+
185+
const appointmentData1 = {
186+
text: 'Appointment 1',
187+
startDate: new Date(2015, 1, 9, 8),
188+
endDate: new Date(2015, 1, 9, 9),
189+
};
190+
const appointmentData2 = {
191+
text: 'Appointment 2',
192+
startDate: new Date(2015, 1, 9, 10),
193+
endDate: new Date(2015, 1, 9, 11),
194+
};
195+
196+
await createScheduler({
197+
dataSource: [appointmentData1, appointmentData2],
198+
currentView: 'day',
199+
currentDate: new Date(2015, 1, 9, 8),
200+
appointmentTemplate,
201+
});
202+
203+
expect(appointmentTemplate).toHaveBeenCalledTimes(2);
204+
expect(appointmentTemplate.mock.calls[0]).toHaveLength(3);
205+
expect(appointmentTemplate.mock.calls[0]).toEqual([
206+
expect.objectContaining({
207+
appointmentData: appointmentData1,
208+
targetedAppointmentData: expect.objectContaining({
209+
...appointmentData1,
210+
displayStartDate: appointmentData1.startDate,
211+
displayEndDate: appointmentData1.endDate,
212+
}),
213+
}),
214+
0,
215+
expect.any(HTMLElement),
216+
]);
217+
218+
const container1 = appointmentTemplate.mock.calls[0][2] as HTMLElement;
219+
expect(container1.classList.contains('dx-scheduler-appointment-content')).toBe(true);
220+
expect(container1.innerHTML).toBe('');
221+
222+
expect(appointmentTemplate.mock.calls[1]).toHaveLength(3);
223+
expect(appointmentTemplate.mock.calls[1]).toEqual([
224+
expect.objectContaining({
225+
appointmentData: appointmentData2,
226+
targetedAppointmentData: expect.objectContaining({
227+
...appointmentData2,
228+
displayStartDate: appointmentData2.startDate,
229+
displayEndDate: appointmentData2.endDate,
230+
}),
231+
}),
232+
1,
233+
expect.any(HTMLElement),
234+
]);
235+
236+
const container2 = appointmentTemplate.mock.calls[1][2] as HTMLElement;
237+
expect(container2.classList.contains('dx-scheduler-appointment-content')).toBe(true);
238+
expect(container2.innerHTML).toBe('');
239+
});
240+
});
241+
242+
describe('AppointmentCollectorTemplate', () => {
243+
it('should call template function with correct parameters', async () => {
244+
const appointmentCollectorTemplate = jest.fn();
245+
246+
await createScheduler({
247+
dataSource: [
248+
{
249+
text: 'Appointment 1',
250+
startDate: new Date(2015, 1, 9, 8),
251+
endDate: new Date(2015, 1, 9, 9),
252+
},
253+
{
254+
text: 'Appointment 2',
255+
startDate: new Date(2015, 1, 9, 8),
256+
endDate: new Date(2015, 1, 9, 9),
257+
},
258+
{
259+
text: 'Appointment 3',
260+
startDate: new Date(2015, 1, 9, 8),
261+
endDate: new Date(2015, 1, 9, 9),
262+
},
263+
],
264+
currentView: 'day',
265+
currentDate: new Date(2015, 1, 9, 8),
266+
maxAppointmentsPerCell: 2,
267+
appointmentCollectorTemplate,
268+
});
269+
270+
expect(appointmentCollectorTemplate).toHaveBeenCalledTimes(1);
271+
expect(appointmentCollectorTemplate.mock.calls[0]).toHaveLength(2);
272+
expect(appointmentCollectorTemplate.mock.calls[0]).toEqual([
273+
expect.objectContaining({
274+
appointmentCount: 1,
275+
isCompact: true,
276+
items: [
277+
expect.objectContaining({
278+
text: 'Appointment 3',
279+
}),
280+
],
281+
}),
282+
expect.any(HTMLElement),
283+
]);
284+
285+
const container = appointmentCollectorTemplate.mock.calls[0][1] as HTMLElement;
286+
expect(container.classList.contains('dx-button-content')).toBe(true);
287+
});
288+
});
289+
});
290+
291+
describe('onAppointmentRendered', () => {
292+
it('should call onAppointmentRendered callback', async () => {
293+
const onAppointmentRendered = jest.fn();
294+
295+
await createScheduler({
296+
dataSource: [{
297+
text: 'Appointment 1',
298+
startDate: new Date(2015, 1, 9, 8),
299+
endDate: new Date(2015, 1, 9, 9),
300+
}],
301+
currentView: 'day',
302+
currentDate: new Date(2015, 1, 9, 8),
303+
onAppointmentRendered,
304+
});
305+
306+
expect(onAppointmentRendered).toHaveBeenCalledTimes(1);
307+
});
308+
309+
it('should call onAppointmentRendered after .option() change', async () => {
310+
const { scheduler } = await createScheduler({
311+
dataSource: [{
312+
text: 'Appointment 1',
313+
startDate: new Date(2015, 1, 9, 8),
314+
endDate: new Date(2015, 1, 9, 9),
315+
}],
316+
currentView: 'day',
317+
currentDate: new Date(2015, 1, 9, 8),
318+
});
319+
320+
const onAppointmentRendered = jest.fn();
321+
scheduler.option('onAppointmentRendered', onAppointmentRendered);
322+
scheduler.repaint();
323+
await new Promise(process.nextTick);
324+
325+
expect(onAppointmentRendered).toHaveBeenCalledTimes(1);
326+
});
327+
});
328+
});

packages/devextreme/js/__internal/scheduler/appointments_new/__mock__/appointment_properties.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const getBaseAppointmentProperties = (
1616
};
1717

1818
const config: BaseAppointmentViewProperties = {
19+
index: 0,
1920
appointmentData,
2021
targetedAppointmentData: normalizedTargetedAppointmentData,
2122
appointmentTemplate: new EmptyTemplate(),

packages/devextreme/js/__internal/scheduler/appointments_new/appointment/agenda_appointment.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export class AgendaAppointmentView extends BaseAppointmentView<AgendaAppointment
5050

5151
private renderMarker($container: dxElementWrapper): void {
5252
const $leftContainer = $('<div>')
53-
.addClass('dx-scheduler-agenda-appointment-left-layout')
53+
.addClass(AGENDA_APPOINTMENT_CLASSES.LEFT_LAYOUT)
5454
.appendTo($container);
5555

5656
const $marker = $('<div>')
@@ -74,7 +74,7 @@ export class AgendaAppointmentView extends BaseAppointmentView<AgendaAppointment
7474

7575
private renderInfo($container: dxElementWrapper): void {
7676
const $rightContainer = $('<div>')
77-
.addClass('dx-scheduler-agenda-appointment-right-layout')
77+
.addClass(AGENDA_APPOINTMENT_CLASSES.RIGHT_LAYOUT)
7878
.appendTo($container);
7979

8080
$('<div>')

0 commit comments

Comments
 (0)