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

(-)a/t/cypress/integration/Biblio/bookingsModal_spec.ts (-1 / +359 lines)
Line 0 Link Here
0
- 
1
describe("Booking Modal Tests", () => {
2
    // Test data setup
3
    const testData = {
4
        biblionumber: "134",
5
        patronId: "19",
6
        pickupLibraryId: "CPL",
7
        itemNumber: "287",
8
        itemTypeId: "BK",
9
        startDate: new Date(),
10
        endDate: new Date(new Date().setDate(new Date().getDate() + 7)), // 7 days from now
11
    };
12
13
    beforeEach(() => {
14
        cy.login();
15
        cy.title().should("eq", "Koha staff interface");
16
17
        // Visit the page with the booking modal
18
        cy.visit(
19
            "/cgi-bin/koha/catalogue/detail.pl?biblionumber=" +
20
                testData.biblionumber
21
        );
22
23
        // Intercept API calls and provide mock responses
24
        cy.intercept("GET", "/api/v1/biblios/*/items?bookable=1&_per_page=-1", {
25
            fixture: "bookings/bookable_items.json",
26
        }).as("getBookableItems");
27
28
        cy.intercept("GET", "/api/v1/bookings?biblio_id=*&_per_page=-1*", {
29
            fixture: "bookings/bookings.json",
30
        }).as("getBookings");
31
32
        cy.intercept("GET", "/api/v1/biblios/*/checkouts?_per_page=-1", {
33
            fixture: "bookings/checkouts.json",
34
        }).as("getCheckouts");
35
36
        cy.intercept("GET", "/api/v1/patrons/*", {
37
            fixture: "bookings/patron.json",
38
        }).as("getPatron");
39
40
        cy.intercept("GET", "/api/v1/biblios/*/pickup_locations*", {
41
            fixture: "bookings/pickup_locations.json",
42
        }).as("getPickupLocations");
43
44
        cy.intercept("GET", "/api/v1/circulation_rules*", {
45
            fixture: "bookings/circulation_rules.json",
46
        }).as("getCirculationRules");
47
48
        cy.intercept("POST", "/api/v1/bookings", {
49
            statusCode: 201,
50
            body: {
51
                booking_id: "1001",
52
                start_date: testData.startDate.toISOString(),
53
                end_date: testData.endDate.toISOString(),
54
                pickup_library_id: testData.pickupLibraryId,
55
                biblio_id: testData.biblionumber,
56
                item_id: testData.itemNumber,
57
                patron_id: testData.patronId,
58
            },
59
        }).as("createBooking");
60
61
        cy.intercept("PUT", "/api/v1/bookings/*", {
62
            statusCode: 200,
63
            body: {
64
                booking_id: "1001",
65
                start_date: testData.startDate.toISOString(),
66
                end_date: testData.endDate.toISOString(),
67
                pickup_library_id: testData.pickupLibraryId,
68
                biblio_id: testData.biblionumber,
69
                item_id: testData.itemNumber,
70
                patron_id: testData.patronId,
71
            },
72
        }).as("updateBooking");
73
74
        // Populate the select2 search results for patron search
75
        cy.intercept("GET", "/api/v1/patrons*", {
76
            body: [
77
                {
78
                    patron_id: testData.patronId,
79
                    surname: "Doe",
80
                    firstname: "John",
81
                    cardnumber: "12345",
82
                    library_id: "1",
83
                    library: {
84
                        name: "Main Library",
85
                    },
86
                    category_id: "1",
87
                    date_of_birth: "1990-01-01",
88
                },
89
            ],
90
            pagination: { more: false },
91
        }).as("searchPatrons");
92
93
        // Add clickable button
94
        cy.document().then(doc => {
95
            const button = doc.createElement("button");
96
            button.setAttribute("data-bs-toggle", "modal");
97
            button.setAttribute("data-bs-target", "#placeBookingModal");
98
            button.setAttribute("data-biblionumber", testData.biblionumber);
99
            button.setAttribute("id", "placebooking");
100
            doc.body.appendChild(button);
101
        });
102
103
        // Open the booking modal
104
        cy.get("#placebooking").click();
105
    });
106
107
    it("should load the booking modal correctly", () => {
108
        // Check modal title
109
        cy.get("#placeBookingLabel").should("contain", "Place booking");
110
111
        // Check form elements are present
112
        cy.get("#booking_patron_id").should("exist");
113
        cy.get("#pickup_library_id").should("exist");
114
        cy.get("#booking_itemtype").should("exist");
115
        cy.get("#booking_item_id").should("exist");
116
        cy.get("#period").should("exist");
117
118
        // Check hidden fields
119
        cy.get("#booking_biblio_id").should(
120
            "have.value",
121
            testData.biblionumber
122
        );
123
        cy.get("#booking_start_date").should("have.value", "");
124
        cy.get("#booking_end_date").should("have.value", "");
125
    });
126
127
    it("should enable fields in proper sequence", () => {
128
        // Initially only patron field should be enabled
129
        cy.get("#booking_patron_id").should("not.be.disabled");
130
        cy.get("#pickup_library_id").should("be.disabled");
131
        cy.get("#booking_itemtype").should("be.disabled");
132
        cy.get("#booking_item_id").should("be.disabled");
133
        cy.get("#period").should("be.disabled");
134
135
        // Select patron
136
        cy.selectFromSelect2ByIndex("#booking_patron_id", 0, "John");
137
        //cy.getSelect2("#booking_patron_id")
138
        //    .select2({ search: "John" })
139
        //    .select2({ selectIndex: 0 });
140
        cy.wait("@getPickupLocations");
141
142
        // After patron selection, pickup location should be enabled
143
        cy.get("#pickup_library_id").should("not.be.disabled");
144
145
        // Select pickup location
146
        cy.get("#pickup_library_id").select(testData.pickupLibraryId);
147
        cy.wait("@getCirculationRules");
148
149
        // After pickup location selection, item type should be enabled
150
        cy.get("#booking_itemtype").should("not.be.disabled");
151
152
        // Select item type
153
        cy.get("#booking_itemtype").select(testData.itemTypeId);
154
155
        // After item type selection, item should be enabled
156
        cy.get("#booking_item_id").should("not.be.disabled");
157
158
        // Select item
159
        cy.get("#booking_item_id").select(testData.itemNumber);
160
161
        // After all selections, date picker should be enabled
162
        cy.get("#period").should("not.be.disabled");
163
    });
164
165
    it("should handle item type and item dependencies correctly", () => {
166
        // Select patron and pickup location first
167
        cy.select2Select("booking_patron_id", "John", { index: 0 });
168
        cy.wait("@getPickupLocations");
169
        cy.get("#pickup_library_id").select(testData.pickupLibraryId);
170
        cy.wait("@getCirculationRules");
171
172
        // Select an item first
173
        cy.get("#booking_item_id").select(testData.itemNumber);
174
175
        // Verify that item type gets selected automatically
176
        cy.get("#booking_itemtype").should("have.value", testData.itemTypeId);
177
178
        // Verify that item type gets disabled
179
        cy.get("#booking_itemtype").should("be.disabled");
180
181
        // Reset the modal
182
        cy.get("#placeBookingModal button.close").click();
183
        cy.get(
184
            'button[data-biblionumber="' + testData.biblionumber + '"]'
185
        ).click();
186
187
        // Now select patron, pickup and item type first
188
        cy.select2Select("booking_patron_id", "John", { index: 0 });
189
        cy.wait("@getPickupLocations");
190
        cy.get("#pickup_library_id").select(testData.pickupLibraryId);
191
        cy.wait("@getCirculationRules");
192
        cy.get("#booking_itemtype").select(testData.itemTypeId);
193
194
        // Verify that only items of selected type are enabled
195
        cy.get("#booking_item_id option").then($options => {
196
            const enabledOptions = $options.filter(":not(:disabled)");
197
            enabledOptions.each(function () {
198
                const $option = cy.wrap(this);
199
                $option.should(
200
                    "have.attr",
201
                    "data-itemtype",
202
                    testData.itemTypeId
203
                );
204
            });
205
        });
206
    });
207
208
    it("should handle date selection and availability", () => {
209
        // Select patron, pickup location, item type, and item
210
        cy.select2Select("booking_patron_id", "John", { index: 0 });
211
        cy.wait("@getPickupLocations");
212
        cy.get("#pickup_library_id").select(testData.pickupLibraryId);
213
        cy.wait("@getCirculationRules");
214
        cy.get("#booking_itemtype").select(testData.itemTypeId);
215
        cy.get("#booking_item_id").select(testData.itemNumber);
216
217
        // Get the flatpickr instance and select dates
218
        cy.window().then(win => {
219
            const picker = win.document.getElementById("period")._flatpickr;
220
            const startDate = new Date(testData.startDate);
221
            const endDate = new Date(testData.endDate);
222
223
            picker.setDate([startDate, endDate], true);
224
225
            // Verify the hidden fields are set correctly
226
            cy.get("#booking_start_date").should("not.have.value", "");
227
            cy.get("#booking_end_date").should("not.have.value", "");
228
229
            // Check for disabled dates due to other bookings
230
            cy.get(".flatpickr-day.flatpickr-disabled").should("exist");
231
232
            // Check for event dots indicating existing bookings
233
            cy.get(".flatpickr-day .event-dots").should("exist");
234
        });
235
    });
236
237
    it("should submit a new booking successfully", () => {
238
        // Fill out the booking form
239
        cy.select2Select("booking_patron_id", "John", { index: 0 });
240
        cy.wait("@getPickupLocations");
241
        cy.get("#pickup_library_id").select(testData.pickupLibraryId);
242
        cy.wait("@getCirculationRules");
243
        cy.get("#booking_itemtype").select(testData.itemTypeId);
244
        cy.get("#booking_item_id").select(testData.itemNumber);
245
246
        // Set dates with flatpickr
247
        cy.window().then(win => {
248
            const picker = win.document.getElementById("period")._flatpickr;
249
            const startDate = new Date(testData.startDate);
250
            const endDate = new Date(testData.endDate);
251
            picker.setDate([startDate, endDate], true);
252
        });
253
254
        // Submit the form
255
        cy.get("#placeBookingForm").submit();
256
        cy.wait("@createBooking");
257
258
        // Check success message
259
        cy.get("#transient_result").should(
260
            "contain",
261
            "Booking successfully placed"
262
        );
263
264
        // Check modal closes
265
        cy.get("#placeBookingModal").should("not.be.visible");
266
    });
267
268
    it("should edit an existing booking successfully", () => {
269
        // Open edit booking modal
270
        cy.get("#test-modal-btn")
271
            .invoke("attr", "data-booking", "1001")
272
            .click();
273
274
        // Check modal title for edit
275
        cy.get("#placeBookingLabel").should("contain", "Edit booking");
276
277
        // Verify booking ID is set
278
        cy.get("#booking_id").should("have.value", "1001");
279
280
        // Change pickup location
281
        cy.get("#pickup_library_id").select("2");
282
283
        // Submit the form
284
        cy.get("#placeBookingForm").submit();
285
        cy.wait("@updateBooking");
286
287
        // Check success message
288
        cy.get("#transient_result").should(
289
            "contain",
290
            "Booking successfully updated"
291
        );
292
293
        // Check modal closes
294
        cy.get("#placeBookingModal").should("not.be.visible");
295
    });
296
297
    it("should handle booking failure gracefully", () => {
298
        // Override the create booking intercept to return an error
299
        cy.intercept("POST", "/api/v1/bookings", {
300
            statusCode: 400,
301
            body: {
302
                error: "Booking failed",
303
            },
304
        }).as("failedBooking");
305
306
        // Fill out the booking form
307
        cy.select2Select("booking_patron_id", "John", { index: 0 });
308
        cy.wait("@getPickupLocations");
309
        cy.get("#pickup_library_id").select(testData.pickupLibraryId);
310
        cy.wait("@getCirculationRules");
311
        cy.get("#booking_itemtype").select(testData.itemTypeId);
312
        cy.get("#booking_item_id").select(testData.itemNumber);
313
314
        // Set dates with flatpickr
315
        cy.window().then(win => {
316
            const picker = win.document.getElementById("period")._flatpickr;
317
            const startDate = new Date(testData.startDate);
318
            const endDate = new Date(testData.endDate);
319
            picker.setDate([startDate, endDate], true);
320
        });
321
322
        // Submit the form
323
        cy.get("#placeBookingForm").submit();
324
        cy.wait("@failedBooking");
325
326
        // Check error message
327
        cy.get("#booking_result").should("contain", "Failure");
328
329
        // Modal should remain open
330
        cy.get("#placeBookingModal").should("be.visible");
331
    });
332
333
    it("should reset form when modal is closed", () => {
334
        // Fill out some form fields
335
        cy.getSelect2("#booking_patron_id")
336
            .select2({ search: "John" })
337
            .select2({ selectIndex: 0 });
338
        cy.wait("@getPickupLocations");
339
        cy.get("#pickup_library_id").select(testData.pickupLibraryId);
340
341
        // Close the modal
342
        cy.get("#placeBookingModal .modal-header .close").click();
343
344
        // Re-open the modal
345
        cy.get(
346
            'button[data-biblionumber="' + testData.biblionumber + '"]'
347
        ).click();
348
349
        // Check fields are reset
350
        cy.get("#booking_patron_id").should("have.value", "");
351
        cy.get("#pickup_library_id").should("be.disabled");
352
        cy.get("#booking_itemtype").should("be.disabled");
353
        cy.get("#booking_item_id").should("be.disabled");
354
        cy.get("#period").should("be.disabled");
355
        cy.get("#booking_start_date").should("have.value", "");
356
        cy.get("#booking_end_date").should("have.value", "");
357
        cy.get("#booking_id").should("have.value", "");
358
    });
359
});

Return to bug 39916