Skip to content

Commit 70fca55

Browse files
Scheduler: refactor integration appointment form popup test (#31138)
1 parent bd9efe6 commit 70fca55

File tree

6 files changed

+1018
-26
lines changed

6 files changed

+1018
-26
lines changed

packages/devextreme/js/__internal/scheduler/__tests__/__mock__/create_scheduler.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const createScheduler = async (config: Config): Promise<{
1414
const container = document.createElement('div');
1515
const scheduler = new Scheduler(container, config);
1616
await new Promise(process.nextTick);
17+
document.body.appendChild(container);
1718

1819
return {
1920
container,
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
export class PopupModel {
2+
element: HTMLDivElement;
3+
4+
constructor(element: HTMLDivElement) {
5+
this.element = element;
6+
}
7+
8+
getLabelIdByText = (labelText: string): string => {
9+
const labels = Array.from(this.element.querySelectorAll('label'));
10+
11+
const label = labels.find((l) => l?.textContent?.trim()?.startsWith(labelText));
12+
13+
if (!label) {
14+
throw new Error(`Label with text "${labelText}" not found`);
15+
}
16+
17+
const forId = label.getAttribute('for');
18+
if (!forId) {
19+
throw new Error(`Label with text "${labelText}" has no "for" attribute`);
20+
}
21+
return forId;
22+
};
23+
24+
getInputByLabel = (labelText: string): HTMLInputElement => {
25+
const forId = this.getLabelIdByText(labelText);
26+
27+
const input = this.element.querySelector(`input#${forId}`) as HTMLInputElement;
28+
29+
if (!input) {
30+
throw new Error(`Input with id "${forId}" not found`);
31+
}
32+
33+
return input;
34+
};
35+
36+
setInputValueByLabel = (labelText: string, value: string): HTMLInputElement => {
37+
const input = this.getInputByLabel(labelText);
38+
if (!input) {
39+
throw new Error(`Input with label "${labelText}" not found`);
40+
}
41+
input.value = '';
42+
43+
value.split('').forEach((char) => {
44+
input.value += char;
45+
input.dispatchEvent(new Event('input', { bubbles: true }));
46+
input.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, key: char }));
47+
input.dispatchEvent(new KeyboardEvent('keypress', { bubbles: true, key: char }));
48+
input.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true, key: char }));
49+
});
50+
51+
input.dispatchEvent(new Event('change', { bubbles: true }));
52+
input.dispatchEvent(new Event('input', { bubbles: true }));
53+
54+
return input;
55+
};
56+
57+
getSwitchByName = (name: string): HTMLInputElement => {
58+
const hiddenInput = this.element.querySelector<HTMLInputElement>(`input[name=${name}]`);
59+
60+
if (!hiddenInput) {
61+
throw new Error(`Switch with name "${name}" not found`);
62+
}
63+
64+
return hiddenInput;
65+
};
66+
67+
selectRadio = (value: string): Element | null => {
68+
const group = this.element.querySelector('[role="radiogroup"]');
69+
if (!group) throw new Error('Radiogroup not found');
70+
71+
const radios = Array.from(group.querySelectorAll('[role="radio"]'));
72+
73+
const target = radios.find((radio) => {
74+
const label = radio.getAttribute('aria-label')?.trim();
75+
const text = radio.textContent?.trim();
76+
return label === value || text === value;
77+
});
78+
79+
if (!target) throw new Error(`Radio with value "${value}" not found`);
80+
81+
radios.forEach((r) => {
82+
r.setAttribute('aria-checked', 'false');
83+
r.classList.remove('dx-item-selected', 'dx-radiobutton-checked');
84+
});
85+
86+
target.setAttribute('aria-checked', 'true');
87+
target.classList.add('dx-item-selected', 'dx-radiobutton-checked');
88+
89+
target.dispatchEvent(new MouseEvent('click', { bubbles: true }));
90+
91+
return target;
92+
};
93+
94+
getSelectedRadio = (): HTMLElement | null => this.element.querySelector('[role="radio"][aria-checked="true"]');
95+
96+
getSelectedRadioValue = (): string | null => {
97+
const selected = this.getSelectedRadio();
98+
return selected?.getAttribute('aria-label') ?? selected?.textContent?.trim() ?? null;
99+
};
100+
101+
getForm = (): HTMLElement | null => this.element.querySelector('.dx-form');
102+
103+
getTitle = (): HTMLElement | null => document.querySelector('.dx-popup-title');
104+
105+
getDoneButton = (): HTMLButtonElement => {
106+
const doneButton = this.element.querySelector('.dx-button.dx-popup-done') as HTMLButtonElement;
107+
if (!doneButton) {
108+
throw new Error('Done button not found');
109+
}
110+
return doneButton;
111+
};
112+
113+
getCancelButton = (): HTMLButtonElement => {
114+
const cancelButton = this.element.querySelector('.dx-button.dx-popup-cancel') as HTMLButtonElement;
115+
if (!cancelButton) {
116+
throw new Error('Cancel button not found');
117+
}
118+
return cancelButton;
119+
};
120+
121+
getFormEditor = (fieldName: string): HTMLElement | null => {
122+
const form = this.getForm();
123+
if (form === null) {
124+
return null;
125+
}
126+
return form.querySelector(`[data-field="${fieldName}"]`);
127+
};
128+
129+
getEditSeriesButton = (): HTMLElement => {
130+
const editSeriesButton = document.querySelector('[aria-label="Edit series"]') as HTMLElement;
131+
if (!editSeriesButton) {
132+
throw new Error('Edit series button not found');
133+
}
134+
return editSeriesButton;
135+
};
136+
}
Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,92 @@
1+
import { APPOINTMENT_POPUP_CLASS } from '../../../appointment_popup/m_popup';
2+
import { POPUP_DIALOG_CLASS } from '../../../m_scheduler';
13
import type { AppointmentModel } from './appointment';
24
import { createAppointmentModel } from './appointment';
5+
import { PopupModel } from './popup';
36

47
const getTexts = (
58
cells: NodeListOf<Element>,
69
): string[] => Array.from(cells).map((cell) => cell.textContent?.trim() ?? '');
710

8-
export interface SchedulerModel {
9-
getAppointment: () => AppointmentModel<HTMLDivElement | null>;
10-
getAppointments: () => AppointmentModel[];
11-
getDateTableContent: () => string[];
12-
getHeaderPanelContent: () => string[];
13-
getTimePanelContent: () => string[];
14-
getGroupTableContent: () => string[];
15-
}
11+
export class SchedulerModel {
12+
container: HTMLDivElement;
13+
14+
constructor(container: HTMLDivElement) {
15+
this.container = container;
16+
}
17+
18+
get popup(): PopupModel {
19+
return this.getPopup();
20+
}
21+
22+
getAppointment(text?: string): AppointmentModel<HTMLDivElement | null> {
23+
if (!text) {
24+
return createAppointmentModel(this.container.querySelector('.dx-scheduler-appointment'));
25+
}
26+
return this.getAppointments()
27+
.find((appointment) => appointment.getText() === text) ?? createAppointmentModel(null);
28+
}
1629

17-
export const createSchedulerModel = (container: HTMLDivElement): SchedulerModel => ({
18-
getAppointment(): AppointmentModel<HTMLDivElement | null> {
19-
return createAppointmentModel(container.querySelector('.dx-scheduler-appointment'));
20-
},
2130
getAppointments(): AppointmentModel[] {
22-
return [...container.querySelectorAll('.dx-scheduler-appointment')].map(
31+
return [...this.container.querySelectorAll('.dx-scheduler-appointment')].map(
2332
(element) => createAppointmentModel(element as HTMLDivElement),
2433
);
25-
},
34+
}
35+
2636
getDateTableContent(): string[] {
27-
const cells = container.querySelectorAll('.dx-scheduler-date-table-cell');
37+
const cells = this.container.querySelectorAll('.dx-scheduler-date-table-cell');
2838
return getTexts(cells);
29-
},
39+
}
40+
3041
getHeaderPanelContent(): string[] {
31-
const cells = container.querySelectorAll('.dx-scheduler-header-panel-cell');
42+
const cells = this.container.querySelectorAll('.dx-scheduler-header-panel-cell');
3243
return getTexts(cells);
33-
},
44+
}
45+
3446
getTimePanelContent(): string[] {
35-
const cells = container.querySelectorAll('.dx-scheduler-time-panel-cell');
47+
const cells = this.container.querySelectorAll('.dx-scheduler-time-panel-cell');
3648
return getTexts(cells);
37-
},
49+
}
50+
3851
getGroupTableContent(): string[] {
39-
const cells = container.querySelectorAll('.dx-scheduler-group-header');
52+
const cells = this.container.querySelectorAll('.dx-scheduler-group-header');
4053
return getTexts(cells);
41-
},
42-
});
54+
}
55+
56+
private getPopup(): PopupModel {
57+
const elements = this.getPopups();
58+
59+
if (elements.length === 0) {
60+
throw new Error('Popup is not rendered');
61+
}
62+
63+
const popupElement = elements[0] as HTMLDivElement;
64+
65+
return new PopupModel(popupElement);
66+
}
67+
68+
getPopups = (): NodeListOf<Element> => document.querySelectorAll(`.dx-overlay-wrapper.${APPOINTMENT_POPUP_CLASS}, .dx-overlay-wrapper.${POPUP_DIALOG_CLASS}`);
69+
70+
getLoadPanel = (): HTMLElement | null => document.querySelector('.dx-loadpanel');
71+
72+
getTooltipAppointment = (): HTMLElement | null => document.querySelector('.dx-tooltip-appointment-item');
73+
74+
openPopupByDblClick(text?: string): AppointmentModel {
75+
const appointment = this.getAppointment(text) as AppointmentModel;
76+
77+
if (!appointment?.element) {
78+
throw new Error(`Appointment "${text}" not found`);
79+
}
80+
81+
appointment.element.click();
82+
appointment.element.click();
83+
84+
return appointment;
85+
}
86+
}
87+
88+
export const createSchedulerModel = (container: HTMLDivElement): SchedulerModel => {
89+
const model = new SchedulerModel(container);
90+
91+
return model;
92+
};

0 commit comments

Comments
 (0)