Skip to content

Commit 7a4f8a0

Browse files
committed
refactor(scheduler): address KBN review feedback for appointments_new
1 parent fe04654 commit 7a4f8a0

3 files changed

Lines changed: 164 additions & 28 deletions

File tree

packages/devextreme/js/__internal/scheduler/appointments_new/appointments.focus_controller.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ import { AppointmentCollector } from './appointment_collector';
1010
import type { Appointments } from './appointments';
1111
import type { ViewItem } from './view_item';
1212

13+
interface AppointmentsFocusControllerHandlers {
14+
onAppointmentEnterKeyDown: (appointmentView: BaseAppointmentView, event: DxEvent) => void;
15+
}
16+
1317
export class AppointmentsFocusController {
1418
private focusableSortedIndex = 0;
1519

@@ -27,7 +31,10 @@ export class AppointmentsFocusController {
2731
return this.appointments.option().tabIndex;
2832
}
2933

30-
constructor(private readonly appointments: Appointments) { }
34+
constructor(
35+
private readonly appointments: Appointments,
36+
private readonly handlers: AppointmentsFocusControllerHandlers,
37+
) { }
3138

3239
public onViewItemClick(viewItem: ViewItem): void {
3340
this.focusViewItem(viewItem);
@@ -119,12 +126,9 @@ export class AppointmentsFocusController {
119126
const { allowDelete, onDeleteKeyPress } = this.appointments.option();
120127
if (!allowDelete) { return; }
121128

122-
const sortedItem = this.sortedAppointments[viewItem.option().sortedIndex];
123-
if (!sortedItem) { return; }
124-
125129
const appointmentViewItem = viewItem as BaseAppointmentView;
126130
onDeleteKeyPress({
127-
appointmentData: sortedItem.itemData,
131+
appointmentData: appointmentViewItem.appointmentData,
128132
targetedAppointmentData: appointmentViewItem.targetedAppointmentData,
129133
});
130134
}
@@ -146,15 +150,16 @@ export class AppointmentsFocusController {
146150
}
147151

148152
private handleEnterKeyDown(viewItem: ViewItem, e: KeyboardKeyDownEvent): void {
149-
const { onItemActivate } = this.appointments.option();
150-
const sortedItem = this.sortedAppointments[viewItem.option().sortedIndex];
151-
if (!sortedItem) { return; }
152153
e.originalEvent.preventDefault();
153-
const appointmentViewItem = viewItem as BaseAppointmentView;
154-
onItemActivate({
155-
data: sortedItem.itemData,
156-
targetedAppointmentData: appointmentViewItem.targetedAppointmentData,
157-
});
154+
155+
if (viewItem instanceof AppointmentCollector) {
156+
return;
157+
}
158+
159+
this.handlers.onAppointmentEnterKeyDown(
160+
viewItem as BaseAppointmentView,
161+
e.originalEvent as DxEvent,
162+
);
158163
}
159164

160165
private focusBySortedItem(sortedItem: SortedEntity): void {

packages/devextreme/js/__internal/scheduler/appointments_new/appointments.test.ts

Lines changed: 143 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,48 @@ describe('Appointments', () => {
945945
expect(wasDefaultPrevented).toBe(true);
946946
});
947947

948+
it('should prevent default browser behavior on Enter key', () => {
949+
const viewModel = [
950+
mockGridViewModel({ ...defaultAppointmentData }, { sortedIndex: 0 }),
951+
];
952+
953+
const instance = createAppointments({
954+
...getProperties(),
955+
});
956+
instance.option('viewModel', viewModel);
957+
958+
const viewItem = instance.getViewItemBySortedIndex(0);
959+
(viewItem?.$element().get(0) as HTMLElement).click();
960+
961+
const wasDefaultPrevented = !fireEvent.keyDown(
962+
viewItem?.$element().get(0) as HTMLElement,
963+
{ key: 'Enter' },
964+
);
965+
966+
expect(wasDefaultPrevented).toBe(true);
967+
});
968+
969+
it('should prevent default browser behavior on Space key', () => {
970+
const viewModel = [
971+
mockGridViewModel({ ...defaultAppointmentData }, { sortedIndex: 0 }),
972+
];
973+
974+
const instance = createAppointments({
975+
...getProperties(),
976+
});
977+
instance.option('viewModel', viewModel);
978+
979+
const viewItem = instance.getViewItemBySortedIndex(0);
980+
(viewItem?.$element().get(0) as HTMLElement).click();
981+
982+
const wasDefaultPrevented = !fireEvent.keyDown(
983+
viewItem?.$element().get(0) as HTMLElement,
984+
{ key: ' ' },
985+
);
986+
987+
expect(wasDefaultPrevented).toBe(true);
988+
});
989+
948990
it('should move focus to last appointment on End key', () => {
949991
const viewModel = [
950992
mockGridViewModel({ ...defaultAppointmentData }, { sortedIndex: 0 }),
@@ -1042,50 +1084,137 @@ describe('Appointments', () => {
10421084
expect(onDeleteKeyPress).not.toHaveBeenCalled();
10431085
});
10441086

1045-
it('should call onItemActivate when Enter is pressed', () => {
1046-
const onItemActivate = jest.fn();
1087+
it('should show appointment popup when Enter is pressed', () => {
1088+
const showEditAppointmentPopup = jest.fn();
10471089
const viewModel = [
10481090
mockGridViewModel({ ...defaultAppointmentData }, { sortedIndex: 0 }),
10491091
];
10501092

10511093
const instance = createAppointments({
10521094
...getProperties(),
1053-
onItemActivate,
1054-
getSortedItems: () => viewModel as unknown as SortedEntity[],
1095+
showEditAppointmentPopup,
10551096
});
10561097
instance.option('viewModel', viewModel);
10571098

10581099
const viewItem = instance.getViewItemBySortedIndex(0);
10591100
(viewItem?.$element().get(0) as HTMLElement).click();
10601101
fireEvent.keyDown(viewItem?.$element().get(0) as HTMLElement, { key: 'Enter' });
10611102

1062-
expect(onItemActivate).toHaveBeenCalledTimes(1);
1063-
expect(onItemActivate).toHaveBeenCalledWith(
1064-
expect.objectContaining({ data: defaultAppointmentData }),
1103+
expect(showEditAppointmentPopup).toHaveBeenCalledTimes(1);
1104+
expect(showEditAppointmentPopup).toHaveBeenCalledWith(
1105+
defaultAppointmentData,
1106+
expect.objectContaining({ ...defaultAppointmentData }),
10651107
);
10661108
});
1067-
it('should call onItemActivate when Space is pressed', () => {
1068-
const onItemActivate = jest.fn();
1109+
1110+
it('should call onAppointmentDblClick when Enter is pressed', () => {
1111+
const onAppointmentDblClick = jest.fn();
10691112
const viewModel = [
10701113
mockGridViewModel({ ...defaultAppointmentData }, { sortedIndex: 0 }),
10711114
];
10721115

10731116
const instance = createAppointments({
10741117
...getProperties(),
1075-
onItemActivate,
1076-
getSortedItems: () => viewModel as unknown as SortedEntity[],
1118+
onAppointmentDblClick,
1119+
});
1120+
instance.option('viewModel', viewModel);
1121+
1122+
const viewItem = instance.getViewItemBySortedIndex(0);
1123+
(viewItem?.$element().get(0) as HTMLElement).click();
1124+
fireEvent.keyDown(viewItem?.$element().get(0) as HTMLElement, { key: 'Enter' });
1125+
1126+
expect(onAppointmentDblClick).toHaveBeenCalledTimes(1);
1127+
expect(onAppointmentDblClick).toHaveBeenCalledWith(
1128+
expect.objectContaining({ appointmentData: defaultAppointmentData }),
1129+
);
1130+
});
1131+
1132+
it('should show appointment popup when Space is pressed', () => {
1133+
const showEditAppointmentPopup = jest.fn();
1134+
const viewModel = [
1135+
mockGridViewModel({ ...defaultAppointmentData }, { sortedIndex: 0 }),
1136+
];
1137+
1138+
const instance = createAppointments({
1139+
...getProperties(),
1140+
showEditAppointmentPopup,
10771141
});
10781142
instance.option('viewModel', viewModel);
10791143

10801144
const viewItem = instance.getViewItemBySortedIndex(0);
10811145
(viewItem?.$element().get(0) as HTMLElement).click();
10821146
fireEvent.keyDown(viewItem?.$element().get(0) as HTMLElement, { key: ' ' });
10831147

1084-
expect(onItemActivate).toHaveBeenCalledTimes(1);
1085-
expect(onItemActivate).toHaveBeenCalledWith(
1086-
expect.objectContaining({ data: defaultAppointmentData }),
1148+
expect(showEditAppointmentPopup).toHaveBeenCalledTimes(1);
1149+
expect(showEditAppointmentPopup).toHaveBeenCalledWith(
1150+
defaultAppointmentData,
1151+
expect.objectContaining({ ...defaultAppointmentData }),
10871152
);
10881153
});
1154+
1155+
it('should call onAppointmentDblClick when Space is pressed', () => {
1156+
const onAppointmentDblClick = jest.fn();
1157+
const viewModel = [
1158+
mockGridViewModel({ ...defaultAppointmentData }, { sortedIndex: 0 }),
1159+
];
1160+
1161+
const instance = createAppointments({
1162+
...getProperties(),
1163+
onAppointmentDblClick,
1164+
});
1165+
instance.option('viewModel', viewModel);
1166+
1167+
const viewItem = instance.getViewItemBySortedIndex(0);
1168+
(viewItem?.$element().get(0) as HTMLElement).click();
1169+
fireEvent.keyDown(viewItem?.$element().get(0) as HTMLElement, { key: ' ' });
1170+
1171+
expect(onAppointmentDblClick).toHaveBeenCalledTimes(1);
1172+
expect(onAppointmentDblClick).toHaveBeenCalledWith(
1173+
expect.objectContaining({ appointmentData: defaultAppointmentData }),
1174+
);
1175+
});
1176+
1177+
it('should show tooltip when Enter is pressed on appointment collector', () => {
1178+
const showTooltipForCollector = jest.fn();
1179+
const showEditAppointmentPopup = jest.fn();
1180+
const viewModel = [
1181+
mockAppointmentCollectorViewModel({ ...defaultAppointmentData }, { sortedIndex: 0 }),
1182+
];
1183+
1184+
const instance = createAppointments({
1185+
...getProperties(),
1186+
showTooltipForCollector,
1187+
showEditAppointmentPopup,
1188+
});
1189+
instance.option('viewModel', viewModel);
1190+
1191+
const viewItem = instance.getViewItemBySortedIndex(0);
1192+
fireEvent.keyDown(viewItem?.$element().get(0) as HTMLElement, { key: 'Enter' });
1193+
1194+
expect(showTooltipForCollector).toHaveBeenCalledTimes(1);
1195+
expect(showEditAppointmentPopup).not.toHaveBeenCalled();
1196+
});
1197+
1198+
it('should show tooltip when Space is pressed on appointment collector', () => {
1199+
const showTooltipForCollector = jest.fn();
1200+
const showEditAppointmentPopup = jest.fn();
1201+
const viewModel = [
1202+
mockAppointmentCollectorViewModel({ ...defaultAppointmentData }, { sortedIndex: 0 }),
1203+
];
1204+
1205+
const instance = createAppointments({
1206+
...getProperties(),
1207+
showTooltipForCollector,
1208+
showEditAppointmentPopup,
1209+
});
1210+
instance.option('viewModel', viewModel);
1211+
1212+
const viewItem = instance.getViewItemBySortedIndex(0);
1213+
fireEvent.keyDown(viewItem?.$element().get(0) as HTMLElement, { key: ' ' });
1214+
1215+
expect(showTooltipForCollector).toHaveBeenCalledTimes(1);
1216+
expect(showEditAppointmentPopup).not.toHaveBeenCalled();
1217+
});
10891218
});
10901219
});
10911220

packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,9 @@ export class Appointments extends DOMComponent<Appointments, AppointmentsPropert
120120
override _init(): void {
121121
super._init();
122122

123-
this.focusController = new AppointmentsFocusController(this);
123+
this.focusController = new AppointmentsFocusController(this, {
124+
onAppointmentEnterKeyDown: this.onAppointmentDblClick.bind(this),
125+
});
124126

125127
this._templateManager.addDefaultTemplates({
126128
appointment: new EmptyTemplate(),

0 commit comments

Comments
 (0)