From 35f82b58cde0f0136974ca4b3510d47777e6ac2e Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Mon, 19 May 2025 13:29:49 +0100 Subject: [PATCH] Bug 39916: Add cypress helpers for flatpickr --- t/cypress/support/e2e.js | 1 + t/cypress/support/flatpickr.js | 333 +++++++++++++++++++++++++++++++++ 2 files changed, 334 insertions(+) create mode 100644 t/cypress/support/flatpickr.js diff --git a/t/cypress/support/e2e.js b/t/cypress/support/e2e.js index 96250ac2bbc..0777212dd42 100644 --- a/t/cypress/support/e2e.js +++ b/t/cypress/support/e2e.js @@ -26,6 +26,7 @@ // Import Select2 helpers import "./select2"; +import "./flatpickr.js"; function get_fallback_login_value(param) { var env_var = param == "username" ? "KOHA_USER" : "KOHA_PASS"; diff --git a/t/cypress/support/flatpickr.js b/t/cypress/support/flatpickr.js new file mode 100644 index 00000000000..ea7f7b57184 --- /dev/null +++ b/t/cypress/support/flatpickr.js @@ -0,0 +1,333 @@ +// flatpickrHelpers.js - Cypress helper methods for interacting with flatpickr date pickers + +/** + * Gets the visible flatpickr input element using the ID of the original hidden input + * @param {string} originalInputSelector - The CSS selector for the original hidden input + * @returns {Cypress.Chainable} - The visible flatpickr input element + */ +Cypress.Commands.add("getFlatpickr", originalInputSelector => { + return cy + .get(originalInputSelector) + .parents() + .find(".flatpickr_wrapper input.flatpickr-input"); +}); + +/** + * Opens a flatpickr calendar by clicking on its visible input element + * @param {string} originalInputSelector - The CSS selector for the original hidden input + * @returns {Cypress.Chainable} - The Cypress chainable object for additional commands + */ +Cypress.Commands.add("openFlatpickr", originalInputSelector => { + cy.getFlatpickr(originalInputSelector).click(); + // Ensure the calendar is open by checking for the flatpickr container + cy.get(".flatpickr-calendar.open").should("be.visible"); + + // Return the originalInputSelector to enable chaining + return cy.wrap(originalInputSelector); +}); + +/** + * Closes an open flatpickr calendar by clicking outside of it + */ +Cypress.Commands.add("closeFlatpickr", () => { + // Click on the body element, but not on the calendar + cy.get("body").click(0, 0); + // Ensure the calendar is closed + cy.get(".flatpickr-calendar.open").should("not.exist"); +}); + +/** + * Clears a flatpickr input by clicking the clear button + * @param {string} originalInputSelector - The CSS selector for the original hidden input + */ +Cypress.Commands.add("clearFlatpickr", originalInputSelector => { + // Click the clear button (the .clear_date element) + cy.get(originalInputSelector).parents().find(".clear_date").click(); + + // Verify both the visible and hidden inputs are cleared + cy.get(originalInputSelector).should("have.value", ""); + cy.getFlatpickr(originalInputSelector).should("have.value", ""); +}); + +/** + * Sets a date in a flatpickr by selecting it from the calendar + * @param {string} originalInputSelector - The CSS selector for the original hidden input + * @param {Date|string} date - The date to select (Date object or YYYY-MM-DD string) + */ +Cypress.Commands.add("selectFlatpickrDate", (originalInputSelector, date) => { + // Convert to Date object if string was provided + const dateObj = typeof date === "string" ? new Date(date) : date; + const year = dateObj.getFullYear(); + const month = dateObj.getMonth(); // 0-based index + const day = dateObj.getDate(); + + // Open the flatpickr calendar + cy.openFlatpickr(originalInputSelector); + + // Navigate to the correct month and year + cy.get(".flatpickr-current-month .cur-month").then($currentMonth => { + cy.get(".flatpickr-current-month .numInput.cur-year").then( + $currentYear => { + const currentMonth = new Date( + $currentMonth.text() + " 1, " + $currentYear.val() + ).getMonth(); + const currentYear = parseInt($currentYear.val()); + + // Navigate to the correct year + const yearDiff = year - currentYear; + if (yearDiff !== 0) { + const arrowSelector = + yearDiff > 0 + ? ".flatpickr-next-month" + : ".flatpickr-prev-month"; + for (let i = 0; i < Math.abs(yearDiff) * 12; i++) { + cy.get(arrowSelector).click(); + cy.wait(100); // Give time for the calendar to update + } + } + + // Navigate to the correct month + const monthDiff = month - currentMonth; + if (monthDiff !== 0) { + const arrowSelector = + monthDiff > 0 + ? ".flatpickr-next-month" + : ".flatpickr-prev-month"; + for (let i = 0; i < Math.abs(monthDiff); i++) { + cy.get(arrowSelector).click(); + cy.wait(100); // Give time for the calendar to update + } + } + } + ); + }); + + // Select the day + cy.get(".flatpickr-day") + .not(".prevMonthDay") + .not(".nextMonthDay") + .contains(day) + .click(); + + // Verify the hidden input has been updated + const formattedDate = `${year}-${(month + 1).toString().padStart(2, "0")}-${day.toString().padStart(2, "0")}`; + cy.get(originalInputSelector).should("have.value", formattedDate); + + // Return the originalInputSelector to enable chaining + return cy.wrap(originalInputSelector); +}); + +/** + * Sets a date in a flatpickr by typing directly into the visible input + * @param {string} originalInputSelector - The CSS selector for the original hidden input + * @param {string} dateString - The date string to type in the format expected by flatpickr + */ +Cypress.Commands.add( + "typeFlatpickrDate", + (originalInputSelector, dateString) => { + // Clear the input and type the new date + cy.getFlatpickr(originalInputSelector).clear().type(dateString); + + // Click away to ensure the date is applied + cy.get("body").click(0, 0); + + // Verify both inputs have been updated + cy.get(originalInputSelector).should("have.value", dateString); + + // Return the originalInputSelector to enable chaining + return cy.wrap(originalInputSelector); + } +); + +/** + * Sets a date range in a flatpickr range picker + * @param {string} originalInputSelector - The CSS selector for the original hidden input + * @param {string|Date} startDate - The start date to select + * @param {string|Date} endDate - The end date to select + */ +Cypress.Commands.add( + "selectFlatpickrDateRange", + (originalInputSelector, startDate, endDate) => { + // Open the flatpickr calendar + cy.openFlatpickr(originalInputSelector); + + // Convert to Date objects if strings were provided + const startDateObj = + typeof startDate === "string" ? new Date(startDate) : startDate; + const endDateObj = + typeof endDate === "string" ? new Date(endDate) : endDate; + + // Select start date + const startDay = startDateObj.getDate(); + cy.get(".flatpickr-day") + .not(".prevMonthDay") + .not(".nextMonthDay") + .contains(startDay) + .click(); + + // Select end date + const endDay = endDateObj.getDate(); + cy.get(".flatpickr-day") + .not(".prevMonthDay") + .not(".nextMonthDay") + .contains(endDay) + .click(); + + // Verify the hidden input has been updated (format depends on your flatpickr configuration) + cy.get(originalInputSelector).should("not.have.value", ""); + + // Return the originalInputSelector to enable chaining + return cy.wrap(originalInputSelector); + } +); + +/** + * Gets the current date from a flatpickr input + * @param {string} originalInputSelector - The CSS selector for the original hidden input + * @returns {string} The date value in the input + */ +Cypress.Commands.add("getFlatpickrValue", originalInputSelector => { + return cy.get(originalInputSelector).invoke("val"); +}); + +/** + * Asserts that a flatpickr input has a specific date value + * @param {string} originalInputSelector - The CSS selector for the original hidden input + * @param {string} expectedDate - The expected date value in the input + */ +Cypress.Commands.add( + "flatpickrShouldHaveValue", + (originalInputSelector, expectedDate) => { + cy.get(originalInputSelector).should("have.value", expectedDate); + } +); + +/** + * Navigates to a specific month/year in an already open flatpickr calendar + * @param {Date|string} date - The date to navigate to (only month/year are used) + * @returns {Cypress.Chainable} - Chainable + */ +Cypress.Commands.add("navigateToMonth", date => { + // Convert to Date object if string was provided + const dateObj = typeof date === "string" ? new Date(date) : date; + const year = dateObj.getFullYear(); + const month = dateObj.getMonth(); // 0-based index + + // Navigate to the correct month and year + cy.get(".flatpickr-current-month .cur-month").then($currentMonth => { + cy.get(".flatpickr-current-month .numInput.cur-year").then( + $currentYear => { + const currentMonth = new Date( + $currentMonth.text() + " 1, " + $currentYear.val() + ).getMonth(); + const currentYear = parseInt($currentYear.val()); + + // Navigate to the correct year + const yearDiff = year - currentYear; + if (yearDiff !== 0) { + const arrowSelector = + yearDiff > 0 + ? ".flatpickr-next-month" + : ".flatpickr-prev-month"; + for (let i = 0; i < Math.abs(yearDiff) * 12; i++) { + cy.get(arrowSelector).click(); + cy.wait(100); // Give time for the calendar to update + } + } + + // Navigate to the correct month + const monthDiff = month - currentMonth; + if (monthDiff !== 0) { + const arrowSelector = + monthDiff > 0 + ? ".flatpickr-next-month" + : ".flatpickr-prev-month"; + for (let i = 0; i < Math.abs(monthDiff); i++) { + cy.get(arrowSelector).click(); + cy.wait(100); // Give time for the calendar to update + } + } + } + ); + }); + + return cy.wrap(date); +}); + +/** + * Selects a day from the currently displayed month in an open flatpickr + * @param {number|string} day - The day of the month to select + * @param {string} [originalInputSelector] - Optional selector to verify the input value + * @returns {Cypress.Chainable} - The originalInputSelector (if provided) for chaining + */ +Cypress.Commands.add("selectDay", (day, originalInputSelector = null) => { + // Convert to number if string was provided + const dayNum = parseInt(day, 10); + + // Select the day + cy.get(".flatpickr-day") + .not(".prevMonthDay") + .not(".nextMonthDay") + .contains(dayNum) + .click(); + + // If originalInputSelector is provided, verify it has a non-empty value + if (originalInputSelector) { + cy.get(originalInputSelector).should("not.have.value", ""); + return cy.wrap(originalInputSelector); + } + + return cy.wrap(dayNum); +}); + +/** + * Goes to the next month in an open flatpickr calendar + * @returns {Cypress.Chainable} - Chainable + */ +Cypress.Commands.add("nextMonth", () => { + cy.get(".flatpickr-next-month").click(); + cy.wait(100); // Give time for the calendar to update + return cy.wrap("next"); +}); + +/** + * Goes to the previous month in an open flatpickr calendar + * @returns {Cypress.Chainable} - Chainable + */ +Cypress.Commands.add("prevMonth", () => { + cy.get(".flatpickr-prev-month").click(); + cy.wait(100); // Give time for the calendar to update + return cy.wrap("prev"); +}); + +/** + * Sets the current year in an open flatpickr calendar + * @param {number|string} year - The year to set + * @returns {Cypress.Chainable} - Chainable + */ +Cypress.Commands.add("setYear", year => { + const yearNum = parseInt(year, 10); + cy.get(".flatpickr-current-month .numInput.cur-year") + .clear() + .type(yearNum.toString(), { force: true }) + .type("{enter}"); + cy.wait(100); // Give time for the calendar to update + return cy.wrap(yearNum); +}); + +/** + * Selects today's date in an open flatpickr calendar + * @param {string} [originalInputSelector] - Optional selector to verify the input value + * @returns {Cypress.Chainable} - The originalInputSelector (if provided) for chaining + */ +Cypress.Commands.add("selectToday", (originalInputSelector = null) => { + cy.get(".flatpickr-day.today").click(); + + // If originalInputSelector is provided, verify it has a non-empty value + if (originalInputSelector) { + cy.get(originalInputSelector).should("not.have.value", ""); + return cy.wrap(originalInputSelector); + } + + return cy.wrap("today"); +}); -- 2.49.0