View | Details | Raw Unified | Return to bug 39916
Collapse All | Expand All

(-)a/t/cypress/fixtures/bookings/bookable_items.json (+29 lines)
Line 0 Link Here
1
[
2
  {
3
    "item_id": "789",
4
    "external_id": "BARCODE789",
5
    "effective_item_type_id": "BK",
6
    "item_type": {
7
      "item_type_id": "BK",
8
      "description": "Book"
9
    }
10
  },
11
  {
12
    "item_id": "790",
13
    "external_id": "BARCODE790",
14
    "effective_item_type_id": "BK",
15
    "item_type": {
16
      "item_type_id": "BK",
17
      "description": "Book"
18
    }
19
  },
20
  {
21
    "item_id": "791",
22
    "external_id": "BARCODE791",
23
    "effective_item_type_id": "DVD",
24
    "item_type": {
25
      "item_type_id": "DVD",
26
      "description": "DVD"
27
    }
28
  }
29
]
(-)a/t/cypress/fixtures/bookings/bookings.json (+32 lines)
Line 0 Link Here
1
[
2
  {
3
    "booking_id": "1001",
4
    "biblio_id": "123",
5
    "patron_id": "456",
6
    "item_id": "789",
7
    "pickup_library_id": "1",
8
    "start_date": "2025-05-01T00:00:00.000Z",
9
    "end_date": "2025-05-05T23:59:59.999Z",
10
    "status": "pending"
11
  },
12
  {
13
    "booking_id": "1002",
14
    "biblio_id": "123",
15
    "patron_id": "457",
16
    "item_id": "790",
17
    "pickup_library_id": "1",
18
    "start_date": "2025-05-06T00:00:00.000Z",
19
    "end_date": "2025-05-10T23:59:59.999Z",
20
    "status": "pending"
21
  },
22
  {
23
    "booking_id": "1003",
24
    "biblio_id": "123",
25
    "patron_id": "458",
26
    "item_id": null,
27
    "pickup_library_id": "1",
28
    "start_date": "2025-05-20T00:00:00.000Z",
29
    "end_date": "2025-05-25T23:59:59.999Z",
30
    "status": "pending"
31
  }
32
]
(-)a/t/cypress/fixtures/bookings/checkouts.json (+9 lines)
Line 0 Link Here
1
[
2
  {
3
    "checkout_id": "5001",
4
    "biblio_id": "123",
5
    "item_id": "791",
6
    "patron_id": "459",
7
    "due_date": "2025-05-30T23:59:59.999Z"
8
  }
9
]
(-)a/t/cypress/fixtures/bookings/circulation_rules.json (+9 lines)
Line 0 Link Here
1
[
2
  {
3
    "bookings_lead_period": 2,
4
    "bookings_trail_period": 1,
5
    "issuelength": 14,
6
    "renewalsallowed": 2,
7
    "renewalperiod": 7
8
  }
9
]
(-)a/t/cypress/fixtures/bookings/patron.json (+12 lines)
Line 0 Link Here
1
{
2
  "patron_id": "456",
3
  "surname": "Doe",
4
  "firstname": "John",
5
  "cardnumber": "12345",
6
  "library_id": "1",
7
  "library": {
8
    "name": "Main Library"
9
  },
10
  "category_id": "1",
11
  "date_of_birth": "1990-01-01"
12
}
(-)a/t/cypress/fixtures/bookings/pickup_locations.json (+20 lines)
Line 0 Link Here
1
[
2
  {
3
    "library_id": "1",
4
    "name": "Main Library",
5
    "needs_override": false,
6
    "pickup_items": [
7
      789,
8
      790
9
    ]
10
  },
11
  {
12
    "library_id": "2",
13
    "name": "Branch Library",
14
    "needs_override": false,
15
    "pickup_items": [
16
      789,
17
      791
18
    ]
19
  }
20
]
(-)a/t/cypress/integration/Biblio/bookingsModal_spec.ts (-1 / +667 lines)
Line 0 Link Here
0
- 
1
const dayjs = require("dayjs"); /* Cannot use our calendar JS code, it's in an include file (!)
2
                                   Also note that moment.js is deprecated */
3
const isSameOrBefore = require("dayjs/plugin/isSameOrBefore");
4
dayjs.extend(isSameOrBefore);
5
6
describe("Booking Modal Tests", () => {
7
    // Test data setup
8
    const testData = {
9
        biblionumber: "134",
10
        patronId: "19",
11
        pickupLibraryId: "CPL",
12
        itemNumber: "287",
13
        itemTypeId: "BK",
14
        startDate: new Date(new Date().setDate(new Date().getDate() + 1)), // 1 day from now
15
        endDate: new Date(new Date().setDate(new Date().getDate() + 5)), // 5 days from now
16
    };
17
18
    beforeEach(() => {
19
        cy.login();
20
        cy.title().should("eq", "Koha staff interface");
21
22
        // Visit the page with the booking modal
23
        cy.visit(
24
            "/cgi-bin/koha/catalogue/detail.pl?biblionumber=" +
25
                testData.biblionumber
26
        );
27
28
        // Intercept API calls and provide mock responses
29
        cy.intercept("GET", "/api/v1/biblios/*/items?bookable=1&_per_page=-1", {
30
            fixture: "bookings/bookable_items.json",
31
        }).as("getBookableItems");
32
33
        cy.fixture("bookings/bookings.json").then(bookings => {
34
            const today = dayjs();
35
36
            // Update the dates in the fixture data relative to today
37
            bookings[0].start_date = today
38
                .add(8, "day")
39
                .startOf("day")
40
                .toISOString(); // Today + 8 days at 00:00
41
            bookings[0].end_date = today
42
                .add(13, "day")
43
                .endOf("day")
44
                .toISOString(); // Today + 13 days at 23:59
45
46
            bookings[1].start_date = today
47
                .add(14, "day")
48
                .startOf("day")
49
                .toISOString(); // Today + 14 days at 00:00
50
            bookings[1].end_date = today
51
                .add(18, "day")
52
                .endOf("day")
53
                .toISOString(); // Today + 18 days at 23:59
54
55
            bookings[2].start_date = today
56
                .add(28, "day")
57
                .startOf("day")
58
                .toISOString(); // Today + 28 days at 00:00
59
            bookings[2].end_date = today
60
                .add(33, "day")
61
                .endOf("day")
62
                .toISOString(); // Today + 33 days at 23:59
63
64
            // Use the modified fixture data in your intercept
65
            cy.intercept("GET", "/api/v1/bookings?biblio_id=*&_per_page=-1*", {
66
                body: bookings,
67
            }).as("getBookings");
68
        });
69
70
        cy.intercept("GET", "/api/v1/biblios/*/checkouts?_per_page=-1", {
71
            fixture: "bookings/checkouts.json",
72
        }).as("getCheckouts");
73
74
        cy.intercept("GET", "/api/v1/patrons/*", {
75
            fixture: "bookings/patron.json",
76
        }).as("getPatron");
77
78
        cy.intercept("GET", "/api/v1/biblios/*/pickup_locations*", {
79
            fixture: "bookings/pickup_locations.json",
80
        }).as("getPickupLocations");
81
82
        cy.intercept("GET", "/api/v1/circulation_rules*", {
83
            fixture: "bookings/circulation_rules.json",
84
        }).as("getCirculationRules");
85
86
        cy.intercept("POST", "/api/v1/bookings", {
87
            statusCode: 201,
88
            body: {
89
                booking_id: "1001",
90
                start_date: testData.startDate.toISOString(),
91
                end_date: testData.endDate.toISOString(),
92
                pickup_library_id: testData.pickupLibraryId,
93
                biblio_id: testData.biblionumber,
94
                item_id: testData.itemNumber,
95
                patron_id: testData.patronId,
96
            },
97
        }).as("createBooking");
98
99
        cy.intercept("PUT", "/api/v1/bookings/*", {
100
            statusCode: 200,
101
            body: {
102
                booking_id: "1001",
103
                start_date: testData.startDate.toISOString(),
104
                end_date: testData.endDate.toISOString(),
105
                pickup_library_id: testData.pickupLibraryId,
106
                biblio_id: testData.biblionumber,
107
                item_id: testData.itemNumber,
108
                patron_id: testData.patronId,
109
            },
110
        }).as("updateBooking");
111
112
        // Populate the select2 search results for patron search
113
        cy.intercept("GET", "/api/v1/patrons*", {
114
            body: [
115
                {
116
                    patron_id: testData.patronId,
117
                    surname: "Doe",
118
                    firstname: "John",
119
                    cardnumber: "12345",
120
                    library_id: "1",
121
                    library: {
122
                        name: "Main Library",
123
                    },
124
                    category_id: "1",
125
                    date_of_birth: "1990-01-01",
126
                },
127
            ],
128
            pagination: { more: false },
129
        }).as("searchPatrons");
130
131
        // Add clickable button
132
        cy.document().then(doc => {
133
            const button = doc.createElement("button");
134
            button.setAttribute("data-bs-toggle", "modal");
135
            button.setAttribute("data-bs-target", "#placeBookingModal");
136
            button.setAttribute("data-biblionumber", testData.biblionumber);
137
            button.setAttribute("id", "placebooking");
138
            doc.body.appendChild(button);
139
        });
140
    });
141
142
    it("should load the booking modal correctly", () => {
143
        // Open the booking modal
144
        cy.get("#placebooking").click();
145
146
        // Check modal title
147
        cy.get("#placeBookingLabel").should("contain", "Place booking");
148
149
        // Check form elements are present
150
        cy.get("#booking_patron_id").should("exist");
151
        cy.get("#pickup_library_id").should("exist");
152
        cy.get("#booking_itemtype").should("exist");
153
        cy.get("#booking_item_id").should("exist");
154
        cy.get("#period").should("exist");
155
156
        // Check hidden fields
157
        cy.get("#booking_biblio_id").should(
158
            "have.value",
159
            testData.biblionumber
160
        );
161
        cy.get("#booking_start_date").should("have.value", "");
162
        cy.get("#booking_end_date").should("have.value", "");
163
    });
164
165
    it("should enable fields in proper sequence", () => {
166
        // Open the booking modal
167
        cy.get("#placebooking").click();
168
169
        // Initially only patron field should be enabled
170
        cy.get("#booking_patron_id").should("not.be.disabled");
171
        cy.get("#pickup_library_id").should("be.disabled");
172
        cy.get("#booking_itemtype").should("be.disabled");
173
        cy.get("#booking_item_id").should("be.disabled");
174
        cy.get("#period").should("be.disabled");
175
176
        // Select patron
177
        cy.selectFromSelect2ByIndex("#booking_patron_id", 0, "John");
178
        //cy.getSelect2("#booking_patron_id")
179
        //    .select2({ search: "John" })
180
        //    .select2({ selectIndex: 0 });
181
        cy.wait("@getPickupLocations");
182
183
        // After patron selection, pickup location, item type and item should be enabled
184
        cy.get("#pickup_library_id").should("not.be.disabled");
185
        cy.get("#booking_itemtype").should("not.be.disabled");
186
        cy.get("#booking_item_id").should("not.be.disabled");
187
        cy.get("#period").should("be.disabled");
188
189
        // Select pickup location
190
        cy.selectFromSelect2ByIndex("#pickup_library_id", 0);
191
192
        // Select item type, trigger circulation rules
193
        cy.selectFromSelect2ByIndex("#booking_itemtype", 0);
194
        cy.wait("@getCirculationRules");
195
196
        // After patron, pickup location and itemtype/item selection, date picker should be enabled
197
        cy.get("#period").should("not.be.disabled");
198
199
        // Clear item type and confirm period is disabled
200
        cy.clearSelect2("#booking_itemtype");
201
        cy.get("#period").should("be.disabled");
202
203
        // Select item, re-enable period
204
        cy.selectFromSelect2ByIndex("#booking_item_id", 1);
205
        cy.get("#period").should("not.be.disabled");
206
    });
207
208
    it("should handle item type and item dependencies correctly", () => {
209
        // Open the booking modal
210
        cy.get("#placebooking").click();
211
212
        // Select patron and pickup location first
213
        cy.selectFromSelect2ByIndex("#booking_patron_id", 0, "John");
214
        cy.wait("@getPickupLocations");
215
        cy.selectFromSelect2ByIndex("#pickup_library_id", 0);
216
217
        // Select an item first
218
        cy.selectFromSelect2ByIndex("#booking_item_id", 1);
219
        cy.wait("@getCirculationRules");
220
221
        // Verify that item type gets selected automatically
222
        cy.get("#booking_itemtype").should("have.value", testData.itemTypeId);
223
224
        // Verify that item type gets disabled
225
        cy.get("#booking_itemtype").should("be.disabled");
226
227
        // Reset the modal
228
        cy.get('#placeBookingModal button[data-bs-dismiss="modal"]')
229
            .first()
230
            .click();
231
        cy.get("#placebooking").click();
232
233
        // Now select patron, pickup and item type first
234
        cy.selectFromSelect2ByIndex("#booking_patron_id", 0, "John");
235
        cy.wait("@getPickupLocations");
236
        cy.selectFromSelect2ByIndex("#pickup_library_id", 0);
237
        cy.selectFromSelect2ByIndex("#booking_itemtype", 0);
238
        cy.wait("@getCirculationRules");
239
        cy.wait(300);
240
241
        // Verify that only 'Any item' option and items of selected type are enabled
242
        cy.get("#booking_item_id > option").then($options => {
243
            const enabledOptions = $options.filter(":not(:disabled)");
244
            enabledOptions.each(function () {
245
                const $option = cy.wrap(this);
246
247
                // Get both the value and the data-itemtype attribute to make decisions
248
                $option.invoke("val").then(value => {
249
                    if (value === "0") {
250
                        // We need to re-wrap the element since invoke('val') changed the subject
251
                        cy.wrap(this).should("contain.text", "Any item");
252
                    } else {
253
                        // Re-wrap the element again for this assertion
254
                        cy.wrap(this).should(
255
                            "have.attr",
256
                            "data-itemtype",
257
                            testData.itemTypeId
258
                        );
259
                    }
260
                });
261
            });
262
        });
263
    });
264
265
    it("should disable dates with existing bookings for same item", () => {
266
        // Open the booking modal and setup initial selections
267
        cy.get("#placebooking").click();
268
        setupModalForDateTesting();
269
270
        // Select an item that has existing bookings
271
        cy.selectFromSelect2ByIndex("#booking_item_id", 1);
272
273
        // Open the flatpickr
274
        cy.openFlatpickr("#period");
275
276
        // Check that dates with existing bookings are disabled
277
        cy.get(".flatpickr-calendar").within(() => {
278
            // Days 8-13 should be disabled (from fixture booking data)
279
            const today = new Date();
280
            const bookedStart = new Date(
281
                today.getTime() + 8 * 24 * 60 * 60 * 1000
282
            );
283
            const bookedEnd = new Date(
284
                today.getTime() + 13 * 24 * 60 * 60 * 1000
285
            );
286
287
            // Check date before booked range is not disabled
288
            const beforeStart = new Date(
289
                bookedStart.getTime() - 24 * 60 * 60 * 1000
290
            );
291
            getDayElement(beforeStart).should(
292
                "not.have.class",
293
                "flatpickr-disabled"
294
            );
295
296
            // Check dates within the booked range are disabled
297
            for (let i = 1; i < 5; i++) {
298
                const checkDate = new Date(
299
                    bookedStart.getTime() + i * 24 * 60 * 60 * 1000
300
                );
301
                getDayElement(checkDate).should(
302
                    "have.class",
303
                    "flatpickr-disabled"
304
                );
305
            }
306
307
            // Check date after booked range is not disabled
308
            const afterEnd = new Date(
309
                bookedEnd.getTime() + 24 * 60 * 60 * 1000
310
            );
311
            getDayElement(afterEnd).should(
312
                "not.have.class",
313
                "flatpickr-disabled"
314
            );
315
        });
316
    });
317
318
    it("should disable dates before today and between today and selected start date", () => {
319
        // Open the booking modal and setup initial selections
320
        cy.get("#placebooking").click();
321
        setupModalForDateTesting();
322
323
        const today = dayjs();
324
325
        // Open the flatpickr
326
        cy.openFlatpickr("#period");
327
328
        cy.get(".flatpickr-calendar").within(() => {
329
            // Find the first visible date in the calendar to determine range
330
            cy.get(".flatpickr-day")
331
                .first()
332
                .then($firstDay => {
333
                    const firstDate = dayjs($firstDay.attr("aria-label"));
334
335
                    // Check all dates from first visible date up to today are disabled
336
                    for (
337
                        let checkDate = firstDate;
338
                        checkDate.isSameOrBefore(today);
339
                        checkDate = checkDate.add(1, "day")
340
                    ) {
341
                        getDayElement(checkDate.toDate()).should(
342
                            "have.class",
343
                            "flatpickr-disabled"
344
                        );
345
                    }
346
347
                    // Check dates after today are enabled (until we make a selection)
348
                    for (
349
                        let checkDate = today.add(1, "day");
350
                        checkDate.isSameOrBefore(today.add(5, "day"));
351
                        checkDate = checkDate.add(1, "day")
352
                    ) {
353
                        getDayElement(checkDate.toDate()).should(
354
                            "not.have.class",
355
                            "flatpickr-disabled"
356
                        );
357
                    }
358
                });
359
        });
360
361
        // Select a start date (3 days from today)
362
        const startDate = today.add(3, "day");
363
        cy.selectFlatpickrDate("#period", startDate.toDate());
364
365
        // Verify dates between today and start date are disabled
366
        cy.get(".flatpickr-calendar").within(() => {
367
            for (
368
                let checkDate = today.add(1, "day");
369
                checkDate.isBefore(startDate);
370
                checkDate = checkDate.add(1, "day")
371
            ) {
372
                getDayElement(checkDate.toDate()).should(
373
                    "have.class",
374
                    "flatpickr-disabled"
375
                );
376
            }
377
378
            // Verify the selected start date itself is not disabled
379
            getDayElement(startDate.toDate()).should(
380
                "not.have.class",
381
                "flatpickr-disabled"
382
            );
383
            getDayElement(startDate.toDate()).should("have.class", "selected");
384
        });
385
    });
386
387
    it("should handle lead and trail period hover highlighting", () => {
388
        // Open the booking modal and setup initial selections
389
        cy.get("#placebooking").click();
390
        setupModalForDateTesting();
391
392
        // Open the flatpickr
393
        cy.openFlatpickr("#period");
394
395
        // Get a future date to hover over
396
        let hoverDate = dayjs();
397
        hoverDate = hoverDate.add(5, "day");
398
399
        // Hover over a date and check for lead/trail highlighting
400
        cy.get(".flatpickr-calendar").within(() => {
401
            getDayElement(hoverDate.toDate()).trigger("mouseover");
402
            cy.wait(100);
403
404
            // Check for lead range classes (assuming 2-day lead period from circulation rules)
405
            cy.get(".leadRange, .leadRangeStart, .leadRangeEnd").should(
406
                "exist"
407
            );
408
409
            // Check for trail range classes (assuming 2-day trail period)
410
            cy.get(".trailRange, .trailRangeStart, .trailRangeEnd").should(
411
                "exist"
412
            );
413
        });
414
    });
415
416
    it("should disable click when lead/trail periods overlap with disabled dates", () => {
417
        // Open the booking modal and setup initial selections
418
        cy.get("#placebooking").click();
419
        setupModalForDateTesting();
420
421
        // Open the flatpickr
422
        cy.openFlatpickr("#period");
423
424
        // Find a date that would have overlapping lead/trail with disabled dates
425
        const today = dayjs();
426
        const problematicDate = today.add(7, "day"); // Just before a booked period
427
428
        cy.get(".flatpickr-calendar").within(() => {
429
            getDayElement(problematicDate.toDate())
430
                .trigger("mouseover")
431
                .should($el => {
432
                    expect(
433
                        $el.hasClass("leadDisable") ||
434
                            $el.hasClass("trailDisable"),
435
                        "element has either leadDisable or trailDisable"
436
                    ).to.be.true;
437
                });
438
        });
439
    });
440
441
    it("should show event dots for dates with existing bookings", () => {
442
        // Open the booking modal and setup initial selections
443
        cy.get("#placebooking").click();
444
        setupModalForDateTesting();
445
446
        // Check for event dots on dates with bookings
447
        cy.get(".flatpickr-calendar").within(() => {
448
            cy.get(".event-dots").should("exist");
449
            cy.get(".event-dots .event").should("exist");
450
        });
451
    });
452
453
    it("should handle date selection and availability", () => {
454
        // Open the booking modal
455
        cy.get("#placebooking").click();
456
        setupModalForDateTesting();
457
458
        // Get today's date
459
        const startDate = new Date();
460
        startDate.setDate(startDate.getDate() + 3);
461
462
        // Get end date (5 days from now)
463
        const endDate = new Date(startDate);
464
        endDate.setDate(startDate.getDate() + 4);
465
466
        cy.selectFlatpickrDateRange("#period", startDate, endDate);
467
468
        // Use should with retry capability instead of a simple assertion
469
        const format = date => date.toISOString().split("T")[0];
470
        cy.get("#period").should(
471
            "have.value",
472
            `${format(startDate)} to ${format(endDate)}`
473
        );
474
475
        // Verify the flatpickr visible input also has value
476
        cy.getFlatpickr("#period").should(
477
            "have.value",
478
            `${format(startDate)} to ${format(endDate)}`
479
        );
480
        // Now check the hidden fields
481
        cy.get("#booking_start_date").should(
482
            "have.value",
483
            dayjs(startDate).startOf("day").toISOString()
484
        );
485
        cy.get("#booking_end_date").should(
486
            "have.value",
487
            dayjs(endDate).endOf("day").toISOString()
488
        );
489
    });
490
491
    it("should submit a new booking successfully", () => {
492
        // Open the booking modal
493
        cy.get("#placebooking").click();
494
        setupModalForDateTesting();
495
496
        // Set dates with flatpickr
497
        cy.window().then(win => {
498
            const picker = win.document.getElementById("period")._flatpickr;
499
            const startDate = new Date(testData.startDate);
500
            const endDate = new Date(testData.endDate);
501
            picker.setDate([startDate, endDate], true);
502
        });
503
504
        // Submit the form
505
        cy.get("#placeBookingForm").submit();
506
        cy.wait("@createBooking");
507
508
        // Check success message
509
        cy.get("#transient_result").should(
510
            "contain",
511
            "Booking successfully placed"
512
        );
513
514
        // Check modal closes
515
        cy.get("#placeBookingModal").should("not.be.visible");
516
    });
517
518
    it("should edit an existing booking successfully", () => {
519
        // Open edit booking modal
520
        cy.get("#placebooking")
521
            .invoke("attr", "data-booking", "1001")
522
            .invoke("attr", "data-patron", "456")
523
            .invoke("attr", "data-itemnumber", "789")
524
            .invoke("attr", "data-pickup_library", "1")
525
            .invoke("attr", "data-start_date", "2025-05-01T00:00:00.000Z")
526
            .invoke("attr", "data-end_date", "2025-05-05T23:59:59.999Z")
527
            .click();
528
        cy.wait(300);
529
530
        // Check modal title for edit
531
        cy.get("#placeBookingLabel").should("contain", "Edit booking");
532
533
        // Verify booking ID is set
534
        cy.get("#booking_id").should("have.value", "1001");
535
536
        // Verify patron ID is set
537
        cy.get("#booking_patron_id").should("have.value", "456");
538
539
        // Verify itemnumber is set
540
        cy.get("#booking_item_id").should("have.value", "789");
541
542
        // Verify pickup_library is set
543
        cy.get("#booking_library_id").should("have.value", "1");
544
545
        // Verify item_type is set
546
        cy.get("#booking_itemtype").should("have.value", "BK");
547
548
        // Change pickup location
549
        cy.get("#pickup_library_id").select("2");
550
551
        // Submit the form
552
        cy.get("#placeBookingForm").submit();
553
        cy.wait("@updateBooking");
554
555
        // Check success message
556
        cy.get("#transient_result").should(
557
            "contain",
558
            "Booking successfully updated"
559
        );
560
561
        // Check modal closes
562
        cy.get("#placeBookingModal").should("not.be.visible");
563
    });
564
565
    it("should handle booking failure gracefully", () => {
566
        // Override the create booking intercept to return an error
567
        cy.intercept("POST", "/api/v1/bookings", {
568
            statusCode: 400,
569
            body: {
570
                error: "Booking failed",
571
            },
572
        }).as("failedBooking");
573
574
        // Open the booking modal
575
        cy.get("#placebooking").click();
576
577
        // Fill out the booking form
578
        cy.selectFromSelect2ByIndex("#booking_patron_id", 0, "John");
579
        cy.wait("@getPickupLocations");
580
        cy.selectFromSelect2ByIndex("#pickup_library_id", 0);
581
        cy.selectFromSelect2ByIndex("#booking_item_id", 1);
582
        cy.wait("@getCirculationRules");
583
584
        // Set dates with flatpickr
585
        cy.window().then(win => {
586
            const picker = win.document.getElementById("period")._flatpickr;
587
            const startDate = new Date(testData.startDate);
588
            const endDate = new Date(testData.endDate);
589
            picker.setDate([startDate, endDate], true);
590
        });
591
592
        // Submit the form
593
        cy.get("#placeBookingForm").submit();
594
        cy.wait("@failedBooking");
595
596
        // Check error message
597
        cy.get("#booking_result").should("contain", "Failure");
598
599
        // Modal should remain open
600
        cy.get("#placeBookingModal").should("be.visible");
601
    });
602
603
    it("should reset form when modal is closed", () => {
604
        // Open the booking modal
605
        cy.get("#placebooking").click();
606
        setupModalForDateTesting();
607
608
        // Close the modal
609
        cy.get('#placeBookingModal button[data-bs-dismiss="modal"]')
610
            .first()
611
            .click();
612
613
        // Re-open the modal
614
        cy.get("#placebooking").click();
615
616
        // Check fields are reset
617
        cy.get("#booking_patron_id").should("have.value", null);
618
        cy.get("#pickup_library_id").should("be.disabled");
619
        cy.get("#booking_itemtype").should("be.disabled");
620
        cy.get("#booking_item_id").should("be.disabled");
621
        cy.get("#period").should("be.disabled");
622
        cy.get("#booking_start_date").should("have.value", "");
623
        cy.get("#booking_end_date").should("have.value", "");
624
        cy.get("#booking_id").should("have.value", "");
625
    });
626
627
    // Helper function to setup modal for date testing
628
    function setupModalForDateTesting() {
629
        // Select patron, pickup location and item
630
        cy.selectFromSelect2ByIndex("#booking_patron_id", 0, "John");
631
        cy.wait("@getPickupLocations");
632
        cy.selectFromSelect2ByIndex("#pickup_library_id", 0);
633
        cy.selectFromSelect2ByIndex("#booking_item_id", 1);
634
        cy.wait("@getCirculationRules");
635
636
        // Wait for flatpickr to be enabled
637
        cy.get("#period").should("not.be.disabled");
638
    }
639
640
    // Helper function to find the day element for a given date
641
    function getDayElement(targetDate) {
642
        const targetYear = targetDate.getFullYear();
643
        const targetMonth = targetDate.getMonth();
644
        const targetDay = targetDate.getDate();
645
646
        const monthNames = [
647
            "January",
648
            "February",
649
            "March",
650
            "April",
651
            "May",
652
            "June",
653
            "July",
654
            "August",
655
            "September",
656
            "October",
657
            "November",
658
            "December",
659
        ];
660
661
        // Format the aria-label for the target date
662
        const formattedDateLabel = `${monthNames[targetMonth]} ${targetDay}, ${targetYear}`;
663
664
        // Select the day using aria-label
665
        return cy.get(`.flatpickr-day[aria-label="${formattedDateLabel}"]`);
666
    }
667
});

Return to bug 39916