Skip to content

Commit 12f391f

Browse files
authored
fix(ui5-calendar): remove auto-focus logic from onAfterRendering() hook (#12465)
With this change we remove the focusing logic in the onAfterRendering() hook; The reason behind that is that the focus method inside onAfterRendering could cause race conditions and may take focus away from the user in some edge cases. We also decided to remove DayPicker.cy.tsx testing page, as ui5-daypicker is private component used for the composition of the ui5-calendar and it is NOT intended for single usage.
1 parent b444706 commit 12f391f

File tree

11 files changed

+300
-155
lines changed

11 files changed

+300
-155
lines changed

packages/main/cypress/specs/Calendar.cy.tsx

Lines changed: 182 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -408,9 +408,19 @@ describe("Calendar general interaction", () => {
408408
.find("[ui5-daypicker]")
409409
.shadow()
410410
.find("[tabindex='0']")
411-
.realClick();
411+
.realClick()
412+
.should("have.focus");
412413

413414
cy.focused().realPress(["Shift", "F4"]);
415+
416+
// Wait for focus to settle before proceeding
417+
cy.get<Calendar>("#calendar1")
418+
.shadow()
419+
.find("[ui5-yearpicker]")
420+
.shadow()
421+
.find("[tabindex='0']")
422+
.should("have.focus");
423+
414424
cy.focused().realPress("PageUp");
415425

416426
cy.get<Calendar>("#calendar1")
@@ -419,6 +429,14 @@ describe("Calendar general interaction", () => {
419429
expect(new Date(_timestamp * 1000)).to.deep.equal(new Date(Date.UTC(1980, 9, 1, 0, 0, 0)));
420430
});
421431

432+
// Wait for focus to settle before proceeding
433+
cy.get<Calendar>("#calendar1")
434+
.shadow()
435+
.find("[ui5-yearpicker]")
436+
.shadow()
437+
.find("[tabindex='0']")
438+
.should("have.focus");
439+
422440
cy.focused().realPress("PageDown");
423441

424442
cy.get<Calendar>("#calendar1")
@@ -441,6 +459,14 @@ describe("Calendar general interaction", () => {
441459
expect(new Date(_timestamp * 1000)).to.deep.equal(new Date(Date.UTC(1998, 9, 16, 0, 0, 0)));
442460
});
443461

462+
// Wait for focus to settle before proceeding
463+
cy.get<Calendar>("#calendar1")
464+
.shadow()
465+
.find("[ui5-yearrangepicker]")
466+
.shadow()
467+
.find("[tabindex='0']")
468+
.should("have.focus");
469+
444470
cy.focused().realPress("PageUp");
445471

446472
cy.get<Calendar>("#calendar1")
@@ -463,6 +489,14 @@ describe("Calendar general interaction", () => {
463489
expect(new Date(_timestamp * 1000)).to.deep.equal(new Date(Date.UTC(1998, 9, 16, 0, 0, 0)));
464490
});
465491

492+
// Wait for focus to settle before proceeding
493+
cy.get<Calendar>("#calendar1")
494+
.shadow()
495+
.find("[ui5-yearrangepicker]")
496+
.shadow()
497+
.find("[tabindex='0']")
498+
.should("have.focus");
499+
466500
cy.focused().realPress("PageDown");
467501

468502
cy.get<Calendar>("#calendar1")
@@ -503,7 +537,8 @@ describe("Calendar general interaction", () => {
503537
cy.get<Calendar>("#calendar1").invoke("prop", "timestamp", timestamp);
504538

505539
cy.ui5CalendarGetDay("#calendar1", timestamp.toString())
506-
.focus();
540+
.focus()
541+
.should("have.focus");
507542

508543
// Select the focused date
509544
cy.focused().realPress("Space");
@@ -1254,3 +1289,148 @@ describe("Calendar accessibility", () => {
12541289
});
12551290
});
12561291
});
1292+
1293+
describe("Day Picker Tests", () => {
1294+
it("Select day with Space", () => {
1295+
cy.mount(<Calendar id="calendar1"></Calendar>);
1296+
1297+
cy.get<Calendar>("#calendar1")
1298+
.shadow()
1299+
.find("[ui5-daypicker]")
1300+
.shadow()
1301+
.find(".ui5-dp-item--now")
1302+
.as("today");
1303+
1304+
cy.get("@today")
1305+
.realClick()
1306+
.should("be.focused")
1307+
.realPress("ArrowRight")
1308+
.realPress("Space");
1309+
1310+
cy.focused()
1311+
.invoke("attr", "data-sap-timestamp")
1312+
.then(timestampAttr => {
1313+
const timestamp = parseInt(timestampAttr!);
1314+
const selectedDate = new Date(timestamp * 1000).getDate();
1315+
const expectedDate = new Date(Date.now() + 24 * 3600 * 1000).getDate();
1316+
expect(selectedDate).to.eq(expectedDate);
1317+
});
1318+
1319+
cy.get<Calendar>("#calendar1")
1320+
.should(($calendar) => {
1321+
const selectedDates = $calendar.prop("selectedDates");
1322+
expect(selectedDates).to.have.length.greaterThan(0);
1323+
});
1324+
});
1325+
1326+
it("Select day with Enter", () => {
1327+
const today = new Date();
1328+
const tomorrow = Math.floor(Date.UTC(today.getFullYear(), today.getMonth(), today.getDate() + 1, 0, 0, 0, 0) / 1000);
1329+
1330+
cy.mount(<Calendar id="calendar1"></Calendar>);
1331+
1332+
cy.get<Calendar>("#calendar1")
1333+
.shadow()
1334+
.find("[ui5-daypicker]")
1335+
.shadow()
1336+
.find(".ui5-dp-item--now")
1337+
.realClick();
1338+
1339+
// Wait for focus to settle before proceeding
1340+
cy.get<Calendar>("#calendar1")
1341+
.shadow()
1342+
.find("[ui5-daypicker]")
1343+
.shadow()
1344+
.find("[tabindex='0']")
1345+
.should("have.focus");
1346+
1347+
cy.get<Calendar>("#calendar1")
1348+
.realPress("ArrowRight");
1349+
1350+
cy.get<Calendar>("#calendar1")
1351+
.shadow()
1352+
.find("[ui5-daypicker]")
1353+
.shadow()
1354+
.find(`[data-sap-timestamp='${tomorrow}']`)
1355+
.should("have.focus");
1356+
1357+
cy.get<Calendar>("#calendar1")
1358+
.realPress("Enter");
1359+
1360+
// assert the date after today is selected
1361+
cy.get<Calendar>("#calendar1")
1362+
.should(($calendar) => {
1363+
const selectedDates = $calendar.prop("selectedDates");
1364+
expect(selectedDates).to.include(tomorrow);
1365+
});
1366+
});
1367+
1368+
it("Day names are correctly displayed", () => {
1369+
cy.mount(<Calendar id="calendar1"></Calendar>);
1370+
1371+
cy.get<Calendar>("#calendar1")
1372+
.shadow()
1373+
.find("[ui5-daypicker]")
1374+
.shadow()
1375+
.find(".ui5-dp-firstday")
1376+
.first()
1377+
.should("have.text", "Sun"); // English default
1378+
});
1379+
1380+
it("Day names container has proper structure", () => {
1381+
cy.mount(<Calendar id="calendar1"></Calendar>);
1382+
1383+
cy.get<Calendar>("#calendar1")
1384+
.shadow()
1385+
.find("[ui5-daypicker]")
1386+
.shadow()
1387+
.find(".ui5-dp-days-names-container")
1388+
.should("exist")
1389+
.find("[role='columnheader']")
1390+
.should("have.length", 8);
1391+
});
1392+
1393+
it("Arrow navigation works in day picker", () => {
1394+
const date = new Date(Date.UTC(2000, 10, 15, 0, 0, 0));
1395+
cy.mount(getDefaultCalendar(date));
1396+
1397+
const timestamp = new Date(Date.UTC(2000, 10, 15, 0, 0, 0)).valueOf() / 1000;
1398+
const nextDayTimestamp = new Date(Date.UTC(2000, 10, 16, 0, 0, 0)).valueOf() / 1000;
1399+
1400+
cy.ui5CalendarGetDay("#calendar1", timestamp.toString())
1401+
.realClick()
1402+
.should("have.focus");
1403+
1404+
cy.focused().realPress("ArrowRight");
1405+
1406+
cy.ui5CalendarGetDay("#calendar1", nextDayTimestamp.toString())
1407+
.should("have.focus");
1408+
1409+
cy.focused().realPress("ArrowLeft");
1410+
1411+
cy.ui5CalendarGetDay("#calendar1", timestamp.toString())
1412+
.should("have.focus");
1413+
});
1414+
1415+
it("Today's date is highlighted correctly", () => {
1416+
cy.mount(<Calendar id="calendar1"></Calendar>);
1417+
1418+
cy.get<Calendar>("#calendar1")
1419+
.shadow()
1420+
.find("[ui5-daypicker]")
1421+
.shadow()
1422+
.find(".ui5-dp-item--now")
1423+
.should("exist")
1424+
.and("be.visible")
1425+
.invoke("attr", "data-sap-timestamp")
1426+
.then(timestampAttr => {
1427+
const timestamp = parseInt(timestampAttr!);
1428+
const todayFromTimestamp = new Date(timestamp * 1000);
1429+
const actualToday = new Date();
1430+
1431+
expect(todayFromTimestamp.getDate()).to.equal(actualToday.getDate());
1432+
expect(todayFromTimestamp.getMonth()).to.equal(actualToday.getMonth());
1433+
expect(todayFromTimestamp.getFullYear()).to.equal(actualToday.getFullYear());
1434+
});
1435+
});
1436+
});

packages/main/cypress/specs/DatePicker.cy.tsx

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -511,12 +511,35 @@ describe("Date Picker Tests", () => {
511511
.as("datePicker")
512512
.ui5DatePickerValueHelpIconPress();
513513

514+
// Focus the day picker's focusable element first
514515
cy.get<DatePicker>("@datePicker")
515516
.shadow()
516517
.find("ui5-calendar")
517518
.as("calendar")
518-
.realPress(["Shift", "F4"])
519-
.realPress("F4");
519+
.shadow()
520+
.find("ui5-daypicker")
521+
.shadow()
522+
.find("[tabindex='0']")
523+
.should("be.visible")
524+
.focus()
525+
.should("have.focus");
526+
527+
cy.focused().realPress(["Shift", "F4"]);
528+
529+
// Wait for year picker to be visible and focused
530+
cy.get("@calendar")
531+
.shadow()
532+
.find("ui5-yearpicker")
533+
.should("be.visible");
534+
535+
cy.get("@calendar")
536+
.shadow()
537+
.find("ui5-yearpicker")
538+
.shadow()
539+
.find("[tabindex='0']")
540+
.should("have.focus");
541+
542+
cy.focused().realPress("F4");
520543

521544
cy.get("@calendar")
522545
.shadow()
@@ -531,12 +554,35 @@ describe("Date Picker Tests", () => {
531554
.as("datePicker")
532555
.ui5DatePickerValueHelpIconPress();
533556

557+
// Focus the day picker's focusable element first
534558
cy.get<DatePicker>("@datePicker")
535559
.shadow()
536560
.find("ui5-calendar")
537561
.as("calendar")
538-
.realPress("F4")
539-
.realPress(["Shift", "F4"]);
562+
.shadow()
563+
.find("ui5-daypicker")
564+
.shadow()
565+
.find("[tabindex='0']")
566+
.should("be.visible")
567+
.focus()
568+
.should("have.focus");
569+
570+
cy.focused().realPress("F4");
571+
572+
// Wait for month picker to be visible and focused
573+
cy.get("@calendar")
574+
.shadow()
575+
.find("ui5-monthpicker")
576+
.should("be.visible");
577+
578+
cy.get("@calendar")
579+
.shadow()
580+
.find("ui5-monthpicker")
581+
.shadow()
582+
.find("[tabindex='0']")
583+
.should("have.focus");
584+
585+
cy.focused().realPress(["Shift", "F4"]);
540586

541587
cy.get("@calendar")
542588
.shadow()

packages/main/cypress/specs/DateRangePicker.cy.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import "../../dist/Assets.js";
1+
import "../../src/Assets.js";
22
import { setLanguage } from "@ui5/webcomponents-base/dist/config/Language.js";
33
import DateRangePicker from "../../src/DateRangePicker.js";
44
import Label from "../../src/Label.js";

packages/main/cypress/specs/DayPicker.cy.tsx

Lines changed: 0 additions & 81 deletions
This file was deleted.

0 commit comments

Comments
 (0)