Bugzilla – Attachment 182773 Details for
Bug 39916
The 'Place booking' modal should have cypress tests
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 39916: WIP Cypress tests for the bookings modal
Bug-39916-WIP-Cypress-tests-for-the-bookings-modal.patch (text/plain), 29.68 KB, created by
Martin Renvoize (ashimema)
on 2025-05-23 15:31:29 UTC
(
hide
)
Description:
Bug 39916: WIP Cypress tests for the bookings modal
Filename:
MIME Type:
Creator:
Martin Renvoize (ashimema)
Created:
2025-05-23 15:31:29 UTC
Size:
29.68 KB
patch
obsolete
>From e6aee64502e74df2bfd8b55b275b55d689f6a9a1 Mon Sep 17 00:00:00 2001 >From: Martin Renvoize <martin.renvoize@openfifth.co.uk> >Date: Fri, 16 May 2025 11:36:47 +0100 >Subject: [PATCH] Bug 39916: WIP Cypress tests for the bookings modal > >Lots of passing tests :) >--- > .../fixtures/bookings/bookable_items.json | 29 + > t/cypress/fixtures/bookings/bookings.json | 32 + > t/cypress/fixtures/bookings/checkouts.json | 9 + > .../fixtures/bookings/circulation_rules.json | 9 + > t/cypress/fixtures/bookings/patron.json | 12 + > .../fixtures/bookings/pickup_locations.json | 20 + > .../integration/Biblio/bookingsModal_spec.ts | 667 ++++++++++++++++++ > 7 files changed, 778 insertions(+) > create mode 100644 t/cypress/fixtures/bookings/bookable_items.json > create mode 100644 t/cypress/fixtures/bookings/bookings.json > create mode 100644 t/cypress/fixtures/bookings/checkouts.json > create mode 100644 t/cypress/fixtures/bookings/circulation_rules.json > create mode 100644 t/cypress/fixtures/bookings/patron.json > create mode 100644 t/cypress/fixtures/bookings/pickup_locations.json > create mode 100644 t/cypress/integration/Biblio/bookingsModal_spec.ts > >diff --git a/t/cypress/fixtures/bookings/bookable_items.json b/t/cypress/fixtures/bookings/bookable_items.json >new file mode 100644 >index 00000000000..bdb61b29732 >--- /dev/null >+++ b/t/cypress/fixtures/bookings/bookable_items.json >@@ -0,0 +1,29 @@ >+[ >+ { >+ "item_id": "789", >+ "external_id": "BARCODE789", >+ "effective_item_type_id": "BK", >+ "item_type": { >+ "item_type_id": "BK", >+ "description": "Book" >+ } >+ }, >+ { >+ "item_id": "790", >+ "external_id": "BARCODE790", >+ "effective_item_type_id": "BK", >+ "item_type": { >+ "item_type_id": "BK", >+ "description": "Book" >+ } >+ }, >+ { >+ "item_id": "791", >+ "external_id": "BARCODE791", >+ "effective_item_type_id": "DVD", >+ "item_type": { >+ "item_type_id": "DVD", >+ "description": "DVD" >+ } >+ } >+] >diff --git a/t/cypress/fixtures/bookings/bookings.json b/t/cypress/fixtures/bookings/bookings.json >new file mode 100644 >index 00000000000..9a9d69c4cad >--- /dev/null >+++ b/t/cypress/fixtures/bookings/bookings.json >@@ -0,0 +1,32 @@ >+[ >+ { >+ "booking_id": "1001", >+ "biblio_id": "123", >+ "patron_id": "456", >+ "item_id": "789", >+ "pickup_library_id": "1", >+ "start_date": "2025-05-01T00:00:00.000Z", >+ "end_date": "2025-05-05T23:59:59.999Z", >+ "status": "pending" >+ }, >+ { >+ "booking_id": "1002", >+ "biblio_id": "123", >+ "patron_id": "457", >+ "item_id": "790", >+ "pickup_library_id": "1", >+ "start_date": "2025-05-06T00:00:00.000Z", >+ "end_date": "2025-05-10T23:59:59.999Z", >+ "status": "pending" >+ }, >+ { >+ "booking_id": "1003", >+ "biblio_id": "123", >+ "patron_id": "458", >+ "item_id": null, >+ "pickup_library_id": "1", >+ "start_date": "2025-05-20T00:00:00.000Z", >+ "end_date": "2025-05-25T23:59:59.999Z", >+ "status": "pending" >+ } >+] >diff --git a/t/cypress/fixtures/bookings/checkouts.json b/t/cypress/fixtures/bookings/checkouts.json >new file mode 100644 >index 00000000000..66da27df25f >--- /dev/null >+++ b/t/cypress/fixtures/bookings/checkouts.json >@@ -0,0 +1,9 @@ >+[ >+ { >+ "checkout_id": "5001", >+ "biblio_id": "123", >+ "item_id": "791", >+ "patron_id": "459", >+ "due_date": "2025-05-30T23:59:59.999Z" >+ } >+] >diff --git a/t/cypress/fixtures/bookings/circulation_rules.json b/t/cypress/fixtures/bookings/circulation_rules.json >new file mode 100644 >index 00000000000..e2896d1e1ff >--- /dev/null >+++ b/t/cypress/fixtures/bookings/circulation_rules.json >@@ -0,0 +1,9 @@ >+[ >+ { >+ "bookings_lead_period": 2, >+ "bookings_trail_period": 1, >+ "issuelength": 14, >+ "renewalsallowed": 2, >+ "renewalperiod": 7 >+ } >+] >diff --git a/t/cypress/fixtures/bookings/patron.json b/t/cypress/fixtures/bookings/patron.json >new file mode 100644 >index 00000000000..bcdb2de24bb >--- /dev/null >+++ b/t/cypress/fixtures/bookings/patron.json >@@ -0,0 +1,12 @@ >+{ >+ "patron_id": "456", >+ "surname": "Doe", >+ "firstname": "John", >+ "cardnumber": "12345", >+ "library_id": "1", >+ "library": { >+ "name": "Main Library" >+ }, >+ "category_id": "1", >+ "date_of_birth": "1990-01-01" >+} >diff --git a/t/cypress/fixtures/bookings/pickup_locations.json b/t/cypress/fixtures/bookings/pickup_locations.json >new file mode 100644 >index 00000000000..a1c660499bc >--- /dev/null >+++ b/t/cypress/fixtures/bookings/pickup_locations.json >@@ -0,0 +1,20 @@ >+[ >+ { >+ "library_id": "1", >+ "name": "Main Library", >+ "needs_override": false, >+ "pickup_items": [ >+ 789, >+ 790 >+ ] >+ }, >+ { >+ "library_id": "2", >+ "name": "Branch Library", >+ "needs_override": false, >+ "pickup_items": [ >+ 789, >+ 791 >+ ] >+ } >+] >diff --git a/t/cypress/integration/Biblio/bookingsModal_spec.ts b/t/cypress/integration/Biblio/bookingsModal_spec.ts >new file mode 100644 >index 00000000000..8288628f3ea >--- /dev/null >+++ b/t/cypress/integration/Biblio/bookingsModal_spec.ts >@@ -0,0 +1,667 @@ >+const dayjs = require("dayjs"); /* Cannot use our calendar JS code, it's in an include file (!) >+ Also note that moment.js is deprecated */ >+const isSameOrBefore = require("dayjs/plugin/isSameOrBefore"); >+dayjs.extend(isSameOrBefore); >+ >+describe("Booking Modal Tests", () => { >+ // Test data setup >+ const testData = { >+ biblionumber: "134", >+ patronId: "19", >+ pickupLibraryId: "CPL", >+ itemNumber: "287", >+ itemTypeId: "BK", >+ startDate: new Date(new Date().setDate(new Date().getDate() + 1)), // 1 day from now >+ endDate: new Date(new Date().setDate(new Date().getDate() + 5)), // 5 days from now >+ }; >+ >+ beforeEach(() => { >+ cy.login(); >+ cy.title().should("eq", "Koha staff interface"); >+ >+ // Visit the page with the booking modal >+ cy.visit( >+ "/cgi-bin/koha/catalogue/detail.pl?biblionumber=" + >+ testData.biblionumber >+ ); >+ >+ // Intercept API calls and provide mock responses >+ cy.intercept("GET", "/api/v1/biblios/*/items?bookable=1&_per_page=-1", { >+ fixture: "bookings/bookable_items.json", >+ }).as("getBookableItems"); >+ >+ cy.fixture("bookings/bookings.json").then(bookings => { >+ const today = dayjs(); >+ >+ // Update the dates in the fixture data relative to today >+ bookings[0].start_date = today >+ .add(8, "day") >+ .startOf("day") >+ .toISOString(); // Today + 8 days at 00:00 >+ bookings[0].end_date = today >+ .add(13, "day") >+ .endOf("day") >+ .toISOString(); // Today + 13 days at 23:59 >+ >+ bookings[1].start_date = today >+ .add(14, "day") >+ .startOf("day") >+ .toISOString(); // Today + 14 days at 00:00 >+ bookings[1].end_date = today >+ .add(18, "day") >+ .endOf("day") >+ .toISOString(); // Today + 18 days at 23:59 >+ >+ bookings[2].start_date = today >+ .add(28, "day") >+ .startOf("day") >+ .toISOString(); // Today + 28 days at 00:00 >+ bookings[2].end_date = today >+ .add(33, "day") >+ .endOf("day") >+ .toISOString(); // Today + 33 days at 23:59 >+ >+ // Use the modified fixture data in your intercept >+ cy.intercept("GET", "/api/v1/bookings?biblio_id=*&_per_page=-1*", { >+ body: bookings, >+ }).as("getBookings"); >+ }); >+ >+ cy.intercept("GET", "/api/v1/biblios/*/checkouts?_per_page=-1", { >+ fixture: "bookings/checkouts.json", >+ }).as("getCheckouts"); >+ >+ cy.intercept("GET", "/api/v1/patrons/*", { >+ fixture: "bookings/patron.json", >+ }).as("getPatron"); >+ >+ cy.intercept("GET", "/api/v1/biblios/*/pickup_locations*", { >+ fixture: "bookings/pickup_locations.json", >+ }).as("getPickupLocations"); >+ >+ cy.intercept("GET", "/api/v1/circulation_rules*", { >+ fixture: "bookings/circulation_rules.json", >+ }).as("getCirculationRules"); >+ >+ cy.intercept("POST", "/api/v1/bookings", { >+ statusCode: 201, >+ body: { >+ booking_id: "1001", >+ start_date: testData.startDate.toISOString(), >+ end_date: testData.endDate.toISOString(), >+ pickup_library_id: testData.pickupLibraryId, >+ biblio_id: testData.biblionumber, >+ item_id: testData.itemNumber, >+ patron_id: testData.patronId, >+ }, >+ }).as("createBooking"); >+ >+ cy.intercept("PUT", "/api/v1/bookings/*", { >+ statusCode: 200, >+ body: { >+ booking_id: "1001", >+ start_date: testData.startDate.toISOString(), >+ end_date: testData.endDate.toISOString(), >+ pickup_library_id: testData.pickupLibraryId, >+ biblio_id: testData.biblionumber, >+ item_id: testData.itemNumber, >+ patron_id: testData.patronId, >+ }, >+ }).as("updateBooking"); >+ >+ // Populate the select2 search results for patron search >+ cy.intercept("GET", "/api/v1/patrons*", { >+ body: [ >+ { >+ patron_id: testData.patronId, >+ surname: "Doe", >+ firstname: "John", >+ cardnumber: "12345", >+ library_id: "1", >+ library: { >+ name: "Main Library", >+ }, >+ category_id: "1", >+ date_of_birth: "1990-01-01", >+ }, >+ ], >+ pagination: { more: false }, >+ }).as("searchPatrons"); >+ >+ // Add clickable button >+ cy.document().then(doc => { >+ const button = doc.createElement("button"); >+ button.setAttribute("data-bs-toggle", "modal"); >+ button.setAttribute("data-bs-target", "#placeBookingModal"); >+ button.setAttribute("data-biblionumber", testData.biblionumber); >+ button.setAttribute("id", "placebooking"); >+ doc.body.appendChild(button); >+ }); >+ }); >+ >+ it("should load the booking modal correctly", () => { >+ // Open the booking modal >+ cy.get("#placebooking").click(); >+ >+ // Check modal title >+ cy.get("#placeBookingLabel").should("contain", "Place booking"); >+ >+ // Check form elements are present >+ cy.get("#booking_patron_id").should("exist"); >+ cy.get("#pickup_library_id").should("exist"); >+ cy.get("#booking_itemtype").should("exist"); >+ cy.get("#booking_item_id").should("exist"); >+ cy.get("#period").should("exist"); >+ >+ // Check hidden fields >+ cy.get("#booking_biblio_id").should( >+ "have.value", >+ testData.biblionumber >+ ); >+ cy.get("#booking_start_date").should("have.value", ""); >+ cy.get("#booking_end_date").should("have.value", ""); >+ }); >+ >+ it("should enable fields in proper sequence", () => { >+ // Open the booking modal >+ cy.get("#placebooking").click(); >+ >+ // Initially only patron field should be enabled >+ cy.get("#booking_patron_id").should("not.be.disabled"); >+ cy.get("#pickup_library_id").should("be.disabled"); >+ cy.get("#booking_itemtype").should("be.disabled"); >+ cy.get("#booking_item_id").should("be.disabled"); >+ cy.get("#period").should("be.disabled"); >+ >+ // Select patron >+ cy.selectFromSelect2ByIndex("#booking_patron_id", 0, "John"); >+ //cy.getSelect2("#booking_patron_id") >+ // .select2({ search: "John" }) >+ // .select2({ selectIndex: 0 }); >+ cy.wait("@getPickupLocations"); >+ >+ // After patron selection, pickup location, item type and item should be enabled >+ cy.get("#pickup_library_id").should("not.be.disabled"); >+ cy.get("#booking_itemtype").should("not.be.disabled"); >+ cy.get("#booking_item_id").should("not.be.disabled"); >+ cy.get("#period").should("be.disabled"); >+ >+ // Select pickup location >+ cy.selectFromSelect2ByIndex("#pickup_library_id", 0); >+ >+ // Select item type, trigger circulation rules >+ cy.selectFromSelect2ByIndex("#booking_itemtype", 0); >+ cy.wait("@getCirculationRules"); >+ >+ // After patron, pickup location and itemtype/item selection, date picker should be enabled >+ cy.get("#period").should("not.be.disabled"); >+ >+ // Clear item type and confirm period is disabled >+ cy.clearSelect2("#booking_itemtype"); >+ cy.get("#period").should("be.disabled"); >+ >+ // Select item, re-enable period >+ cy.selectFromSelect2ByIndex("#booking_item_id", 1); >+ cy.get("#period").should("not.be.disabled"); >+ }); >+ >+ it("should handle item type and item dependencies correctly", () => { >+ // Open the booking modal >+ cy.get("#placebooking").click(); >+ >+ // Select patron and pickup location first >+ cy.selectFromSelect2ByIndex("#booking_patron_id", 0, "John"); >+ cy.wait("@getPickupLocations"); >+ cy.selectFromSelect2ByIndex("#pickup_library_id", 0); >+ >+ // Select an item first >+ cy.selectFromSelect2ByIndex("#booking_item_id", 1); >+ cy.wait("@getCirculationRules"); >+ >+ // Verify that item type gets selected automatically >+ cy.get("#booking_itemtype").should("have.value", testData.itemTypeId); >+ >+ // Verify that item type gets disabled >+ cy.get("#booking_itemtype").should("be.disabled"); >+ >+ // Reset the modal >+ cy.get('#placeBookingModal button[data-bs-dismiss="modal"]') >+ .first() >+ .click(); >+ cy.get("#placebooking").click(); >+ >+ // Now select patron, pickup and item type first >+ cy.selectFromSelect2ByIndex("#booking_patron_id", 0, "John"); >+ cy.wait("@getPickupLocations"); >+ cy.selectFromSelect2ByIndex("#pickup_library_id", 0); >+ cy.selectFromSelect2ByIndex("#booking_itemtype", 0); >+ cy.wait("@getCirculationRules"); >+ cy.wait(300); >+ >+ // Verify that only 'Any item' option and items of selected type are enabled >+ cy.get("#booking_item_id > option").then($options => { >+ const enabledOptions = $options.filter(":not(:disabled)"); >+ enabledOptions.each(function () { >+ const $option = cy.wrap(this); >+ >+ // Get both the value and the data-itemtype attribute to make decisions >+ $option.invoke("val").then(value => { >+ if (value === "0") { >+ // We need to re-wrap the element since invoke('val') changed the subject >+ cy.wrap(this).should("contain.text", "Any item"); >+ } else { >+ // Re-wrap the element again for this assertion >+ cy.wrap(this).should( >+ "have.attr", >+ "data-itemtype", >+ testData.itemTypeId >+ ); >+ } >+ }); >+ }); >+ }); >+ }); >+ >+ it("should disable dates with existing bookings for same item", () => { >+ // Open the booking modal and setup initial selections >+ cy.get("#placebooking").click(); >+ setupModalForDateTesting(); >+ >+ // Select an item that has existing bookings >+ cy.selectFromSelect2ByIndex("#booking_item_id", 1); >+ >+ // Open the flatpickr >+ cy.openFlatpickr("#period"); >+ >+ // Check that dates with existing bookings are disabled >+ cy.get(".flatpickr-calendar").within(() => { >+ // Days 8-13 should be disabled (from fixture booking data) >+ const today = new Date(); >+ const bookedStart = new Date( >+ today.getTime() + 8 * 24 * 60 * 60 * 1000 >+ ); >+ const bookedEnd = new Date( >+ today.getTime() + 13 * 24 * 60 * 60 * 1000 >+ ); >+ >+ // Check date before booked range is not disabled >+ const beforeStart = new Date( >+ bookedStart.getTime() - 24 * 60 * 60 * 1000 >+ ); >+ getDayElement(beforeStart).should( >+ "not.have.class", >+ "flatpickr-disabled" >+ ); >+ >+ // Check dates within the booked range are disabled >+ for (let i = 1; i < 5; i++) { >+ const checkDate = new Date( >+ bookedStart.getTime() + i * 24 * 60 * 60 * 1000 >+ ); >+ getDayElement(checkDate).should( >+ "have.class", >+ "flatpickr-disabled" >+ ); >+ } >+ >+ // Check date after booked range is not disabled >+ const afterEnd = new Date( >+ bookedEnd.getTime() + 24 * 60 * 60 * 1000 >+ ); >+ getDayElement(afterEnd).should( >+ "not.have.class", >+ "flatpickr-disabled" >+ ); >+ }); >+ }); >+ >+ it("should disable dates before today and between today and selected start date", () => { >+ // Open the booking modal and setup initial selections >+ cy.get("#placebooking").click(); >+ setupModalForDateTesting(); >+ >+ const today = dayjs(); >+ >+ // Open the flatpickr >+ cy.openFlatpickr("#period"); >+ >+ cy.get(".flatpickr-calendar").within(() => { >+ // Find the first visible date in the calendar to determine range >+ cy.get(".flatpickr-day") >+ .first() >+ .then($firstDay => { >+ const firstDate = dayjs($firstDay.attr("aria-label")); >+ >+ // Check all dates from first visible date up to today are disabled >+ for ( >+ let checkDate = firstDate; >+ checkDate.isSameOrBefore(today); >+ checkDate = checkDate.add(1, "day") >+ ) { >+ getDayElement(checkDate.toDate()).should( >+ "have.class", >+ "flatpickr-disabled" >+ ); >+ } >+ >+ // Check dates after today are enabled (until we make a selection) >+ for ( >+ let checkDate = today.add(1, "day"); >+ checkDate.isSameOrBefore(today.add(5, "day")); >+ checkDate = checkDate.add(1, "day") >+ ) { >+ getDayElement(checkDate.toDate()).should( >+ "not.have.class", >+ "flatpickr-disabled" >+ ); >+ } >+ }); >+ }); >+ >+ // Select a start date (3 days from today) >+ const startDate = today.add(3, "day"); >+ cy.selectFlatpickrDate("#period", startDate.toDate()); >+ >+ // Verify dates between today and start date are disabled >+ cy.get(".flatpickr-calendar").within(() => { >+ for ( >+ let checkDate = today.add(1, "day"); >+ checkDate.isBefore(startDate); >+ checkDate = checkDate.add(1, "day") >+ ) { >+ getDayElement(checkDate.toDate()).should( >+ "have.class", >+ "flatpickr-disabled" >+ ); >+ } >+ >+ // Verify the selected start date itself is not disabled >+ getDayElement(startDate.toDate()).should( >+ "not.have.class", >+ "flatpickr-disabled" >+ ); >+ getDayElement(startDate.toDate()).should("have.class", "selected"); >+ }); >+ }); >+ >+ it("should handle lead and trail period hover highlighting", () => { >+ // Open the booking modal and setup initial selections >+ cy.get("#placebooking").click(); >+ setupModalForDateTesting(); >+ >+ // Open the flatpickr >+ cy.openFlatpickr("#period"); >+ >+ // Get a future date to hover over >+ let hoverDate = dayjs(); >+ hoverDate = hoverDate.add(5, "day"); >+ >+ // Hover over a date and check for lead/trail highlighting >+ cy.get(".flatpickr-calendar").within(() => { >+ getDayElement(hoverDate.toDate()).trigger("mouseover"); >+ cy.wait(100); >+ >+ // Check for lead range classes (assuming 2-day lead period from circulation rules) >+ cy.get(".leadRange, .leadRangeStart, .leadRangeEnd").should( >+ "exist" >+ ); >+ >+ // Check for trail range classes (assuming 2-day trail period) >+ cy.get(".trailRange, .trailRangeStart, .trailRangeEnd").should( >+ "exist" >+ ); >+ }); >+ }); >+ >+ it("should disable click when lead/trail periods overlap with disabled dates", () => { >+ // Open the booking modal and setup initial selections >+ cy.get("#placebooking").click(); >+ setupModalForDateTesting(); >+ >+ // Open the flatpickr >+ cy.openFlatpickr("#period"); >+ >+ // Find a date that would have overlapping lead/trail with disabled dates >+ const today = dayjs(); >+ const problematicDate = today.add(7, "day"); // Just before a booked period >+ >+ cy.get(".flatpickr-calendar").within(() => { >+ getDayElement(problematicDate.toDate()) >+ .trigger("mouseover") >+ .should($el => { >+ expect( >+ $el.hasClass("leadDisable") || >+ $el.hasClass("trailDisable"), >+ "element has either leadDisable or trailDisable" >+ ).to.be.true; >+ }); >+ }); >+ }); >+ >+ it("should show event dots for dates with existing bookings", () => { >+ // Open the booking modal and setup initial selections >+ cy.get("#placebooking").click(); >+ setupModalForDateTesting(); >+ >+ // Check for event dots on dates with bookings >+ cy.get(".flatpickr-calendar").within(() => { >+ cy.get(".event-dots").should("exist"); >+ cy.get(".event-dots .event").should("exist"); >+ }); >+ }); >+ >+ it("should handle date selection and availability", () => { >+ // Open the booking modal >+ cy.get("#placebooking").click(); >+ setupModalForDateTesting(); >+ >+ // Get today's date >+ const startDate = new Date(); >+ startDate.setDate(startDate.getDate() + 3); >+ >+ // Get end date (5 days from now) >+ const endDate = new Date(startDate); >+ endDate.setDate(startDate.getDate() + 4); >+ >+ cy.selectFlatpickrDateRange("#period", startDate, endDate); >+ >+ // Use should with retry capability instead of a simple assertion >+ const format = date => date.toISOString().split("T")[0]; >+ cy.get("#period").should( >+ "have.value", >+ `${format(startDate)} to ${format(endDate)}` >+ ); >+ >+ // Verify the flatpickr visible input also has value >+ cy.getFlatpickr("#period").should( >+ "have.value", >+ `${format(startDate)} to ${format(endDate)}` >+ ); >+ // Now check the hidden fields >+ cy.get("#booking_start_date").should( >+ "have.value", >+ dayjs(startDate).startOf("day").toISOString() >+ ); >+ cy.get("#booking_end_date").should( >+ "have.value", >+ dayjs(endDate).endOf("day").toISOString() >+ ); >+ }); >+ >+ it("should submit a new booking successfully", () => { >+ // Open the booking modal >+ cy.get("#placebooking").click(); >+ setupModalForDateTesting(); >+ >+ // Set dates with flatpickr >+ cy.window().then(win => { >+ const picker = win.document.getElementById("period")._flatpickr; >+ const startDate = new Date(testData.startDate); >+ const endDate = new Date(testData.endDate); >+ picker.setDate([startDate, endDate], true); >+ }); >+ >+ // Submit the form >+ cy.get("#placeBookingForm").submit(); >+ cy.wait("@createBooking"); >+ >+ // Check success message >+ cy.get("#transient_result").should( >+ "contain", >+ "Booking successfully placed" >+ ); >+ >+ // Check modal closes >+ cy.get("#placeBookingModal").should("not.be.visible"); >+ }); >+ >+ it("should edit an existing booking successfully", () => { >+ // Open edit booking modal >+ cy.get("#placebooking") >+ .invoke("attr", "data-booking", "1001") >+ .invoke("attr", "data-patron", "456") >+ .invoke("attr", "data-itemnumber", "789") >+ .invoke("attr", "data-pickup_library", "1") >+ .invoke("attr", "data-start_date", "2025-05-01T00:00:00.000Z") >+ .invoke("attr", "data-end_date", "2025-05-05T23:59:59.999Z") >+ .click(); >+ cy.wait(300); >+ >+ // Check modal title for edit >+ cy.get("#placeBookingLabel").should("contain", "Edit booking"); >+ >+ // Verify booking ID is set >+ cy.get("#booking_id").should("have.value", "1001"); >+ >+ // Verify patron ID is set >+ cy.get("#booking_patron_id").should("have.value", "456"); >+ >+ // Verify itemnumber is set >+ cy.get("#booking_item_id").should("have.value", "789"); >+ >+ // Verify pickup_library is set >+ cy.get("#booking_library_id").should("have.value", "1"); >+ >+ // Verify item_type is set >+ cy.get("#booking_itemtype").should("have.value", "BK"); >+ >+ // Change pickup location >+ cy.get("#pickup_library_id").select("2"); >+ >+ // Submit the form >+ cy.get("#placeBookingForm").submit(); >+ cy.wait("@updateBooking"); >+ >+ // Check success message >+ cy.get("#transient_result").should( >+ "contain", >+ "Booking successfully updated" >+ ); >+ >+ // Check modal closes >+ cy.get("#placeBookingModal").should("not.be.visible"); >+ }); >+ >+ it("should handle booking failure gracefully", () => { >+ // Override the create booking intercept to return an error >+ cy.intercept("POST", "/api/v1/bookings", { >+ statusCode: 400, >+ body: { >+ error: "Booking failed", >+ }, >+ }).as("failedBooking"); >+ >+ // Open the booking modal >+ cy.get("#placebooking").click(); >+ >+ // Fill out the booking form >+ cy.selectFromSelect2ByIndex("#booking_patron_id", 0, "John"); >+ cy.wait("@getPickupLocations"); >+ cy.selectFromSelect2ByIndex("#pickup_library_id", 0); >+ cy.selectFromSelect2ByIndex("#booking_item_id", 1); >+ cy.wait("@getCirculationRules"); >+ >+ // Set dates with flatpickr >+ cy.window().then(win => { >+ const picker = win.document.getElementById("period")._flatpickr; >+ const startDate = new Date(testData.startDate); >+ const endDate = new Date(testData.endDate); >+ picker.setDate([startDate, endDate], true); >+ }); >+ >+ // Submit the form >+ cy.get("#placeBookingForm").submit(); >+ cy.wait("@failedBooking"); >+ >+ // Check error message >+ cy.get("#booking_result").should("contain", "Failure"); >+ >+ // Modal should remain open >+ cy.get("#placeBookingModal").should("be.visible"); >+ }); >+ >+ it("should reset form when modal is closed", () => { >+ // Open the booking modal >+ cy.get("#placebooking").click(); >+ setupModalForDateTesting(); >+ >+ // Close the modal >+ cy.get('#placeBookingModal button[data-bs-dismiss="modal"]') >+ .first() >+ .click(); >+ >+ // Re-open the modal >+ cy.get("#placebooking").click(); >+ >+ // Check fields are reset >+ cy.get("#booking_patron_id").should("have.value", null); >+ cy.get("#pickup_library_id").should("be.disabled"); >+ cy.get("#booking_itemtype").should("be.disabled"); >+ cy.get("#booking_item_id").should("be.disabled"); >+ cy.get("#period").should("be.disabled"); >+ cy.get("#booking_start_date").should("have.value", ""); >+ cy.get("#booking_end_date").should("have.value", ""); >+ cy.get("#booking_id").should("have.value", ""); >+ }); >+ >+ // Helper function to setup modal for date testing >+ function setupModalForDateTesting() { >+ // Select patron, pickup location and item >+ cy.selectFromSelect2ByIndex("#booking_patron_id", 0, "John"); >+ cy.wait("@getPickupLocations"); >+ cy.selectFromSelect2ByIndex("#pickup_library_id", 0); >+ cy.selectFromSelect2ByIndex("#booking_item_id", 1); >+ cy.wait("@getCirculationRules"); >+ >+ // Wait for flatpickr to be enabled >+ cy.get("#period").should("not.be.disabled"); >+ } >+ >+ // Helper function to find the day element for a given date >+ function getDayElement(targetDate) { >+ const targetYear = targetDate.getFullYear(); >+ const targetMonth = targetDate.getMonth(); >+ const targetDay = targetDate.getDate(); >+ >+ const monthNames = [ >+ "January", >+ "February", >+ "March", >+ "April", >+ "May", >+ "June", >+ "July", >+ "August", >+ "September", >+ "October", >+ "November", >+ "December", >+ ]; >+ >+ // Format the aria-label for the target date >+ const formattedDateLabel = `${monthNames[targetMonth]} ${targetDay}, ${targetYear}`; >+ >+ // Select the day using aria-label >+ return cy.get(`.flatpickr-day[aria-label="${formattedDateLabel}"]`); >+ } >+}); >-- >2.49.0
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 39916
:
182515
|
182516
|
182545
|
182546
|
182635
|
182636
|
182637
|
182771
|
182772
|
182773
|
183206
|
183207
|
183208
|
183209
|
183210