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 |
}); |