|
Lines 453-475
describe("Booking Modal Date Picker Tests", () => {
Link Here
|
| 453 |
|
453 |
|
| 454 |
// Test in clear zone starting at Day 50 to avoid conflicts |
454 |
// Test in clear zone starting at Day 50 to avoid conflicts |
| 455 |
const clearZoneStart = today.add(50, "day"); |
455 |
const clearZoneStart = today.add(50, "day"); |
| 456 |
const calculatedMaxDate = clearZoneStart.add( |
456 |
// Start date counts as day 1 (Koha convention), so max end = start + (maxPeriod - 1) |
|
|
457 |
const maxPeriod = |
| 457 |
dateTestCirculationRules.issuelength + |
458 |
dateTestCirculationRules.issuelength + |
| 458 |
dateTestCirculationRules.renewalsallowed * |
459 |
dateTestCirculationRules.renewalsallowed * |
| 459 |
dateTestCirculationRules.renewalperiod, |
460 |
dateTestCirculationRules.renewalperiod; // 10 + 3*5 = 25 |
|
|
461 |
const calculatedMaxDate = clearZoneStart.add( |
| 462 |
maxPeriod - 1, |
| 460 |
"day" |
463 |
"day" |
| 461 |
); // Day 50 + 25 = Day 75 |
464 |
); // Day 50 + 24 = Day 74 |
| 462 |
|
465 |
|
| 463 |
const beyondMaxDate = calculatedMaxDate.add(1, "day"); // Day 76 |
466 |
const beyondMaxDate = calculatedMaxDate.add(1, "day"); // Day 75 |
| 464 |
|
467 |
|
| 465 |
cy.log( |
468 |
cy.log( |
| 466 |
`Clear zone start: ${clearZoneStart.format("YYYY-MM-DD")} (Day 50)` |
469 |
`Clear zone start: ${clearZoneStart.format("YYYY-MM-DD")} (Day 50)` |
| 467 |
); |
470 |
); |
| 468 |
cy.log( |
471 |
cy.log( |
| 469 |
`Calculated max date: ${calculatedMaxDate.format("YYYY-MM-DD")} (Day 75)` |
472 |
`Calculated max date: ${calculatedMaxDate.format("YYYY-MM-DD")} (Day 74)` |
| 470 |
); |
473 |
); |
| 471 |
cy.log( |
474 |
cy.log( |
| 472 |
`Beyond max date: ${beyondMaxDate.format("YYYY-MM-DD")} (Day 76 - should be disabled)` |
475 |
`Beyond max date: ${beyondMaxDate.format("YYYY-MM-DD")} (Day 75 - should be disabled)` |
| 473 |
); |
476 |
); |
| 474 |
|
477 |
|
| 475 |
// Select the start date to establish context for bold date calculation |
478 |
// Select the start date to establish context for bold date calculation |
|
Lines 477-497
describe("Booking Modal Date Picker Tests", () => {
Link Here
|
| 477 |
clearZoneStart.toDate() |
480 |
clearZoneStart.toDate() |
| 478 |
); |
481 |
); |
| 479 |
|
482 |
|
| 480 |
// Verify max date is selectable |
483 |
// Verify max date enforcement via the flatpickr disable function. |
| 481 |
cy.get("@dateTestFlatpickr") |
484 |
// We query the disable function directly rather than navigating the |
| 482 |
.getFlatpickrDate(calculatedMaxDate.toDate()) |
485 |
// calendar DOM, because the booking modal's reactive system |
| 483 |
.should("not.have.class", "flatpickr-disabled") |
486 |
// (onMonthChange → visibleRangeRef → syncInstanceDatesFromStore → |
| 484 |
.and("be.visible"); |
487 |
// fp.jumpToDate) races with test navigation and pulls the calendar |
| 485 |
|
488 |
// back to the start date's month. |
| 486 |
// Verify beyond max date is disabled (if in visible month range) |
489 |
cy.get("@dateTestFlatpickr").should($el => { |
| 487 |
if ( |
490 |
const fp = $el[0]._flatpickr; |
| 488 |
beyondMaxDate.month() === clearZoneStart.month() || |
491 |
const disableFn = fp.config.disable[0]; |
| 489 |
beyondMaxDate.month() === clearZoneStart.add(1, "month").month() |
492 |
expect(disableFn(calculatedMaxDate.toDate())).to.eq( |
| 490 |
) { |
493 |
false, |
| 491 |
cy.get("@dateTestFlatpickr") |
494 |
`Max date ${calculatedMaxDate.format("YYYY-MM-DD")} should be selectable` |
| 492 |
.getFlatpickrDate(beyondMaxDate.toDate()) |
495 |
); |
| 493 |
.should("have.class", "flatpickr-disabled"); |
496 |
expect(disableFn(beyondMaxDate.toDate())).to.eq( |
| 494 |
} |
497 |
true, |
|
|
498 |
`Beyond max date ${beyondMaxDate.format("YYYY-MM-DD")} should be disabled` |
| 499 |
); |
| 500 |
}); |
| 495 |
|
501 |
|
| 496 |
cy.log("✓ Maximum date calculation enforced correctly"); |
502 |
cy.log("✓ Maximum date calculation enforced correctly"); |
| 497 |
|
503 |
|
|
Lines 527-548
describe("Booking Modal Date Picker Tests", () => {
Link Here
|
| 527 |
`Expected bold dates: ${expectedBoldDates.map(d => d.format("YYYY-MM-DD")).join(", ")}` |
533 |
`Expected bold dates: ${expectedBoldDates.map(d => d.format("YYYY-MM-DD")).join(", ")}` |
| 528 |
); |
534 |
); |
| 529 |
|
535 |
|
| 530 |
// Test each expected bold date has the "booking-loan-boundary" class |
536 |
// Verify bold dates are registered in the instance's loan boundary |
| 531 |
expectedBoldDates.forEach(boldDate => { |
537 |
// cache. We check the cache directly to avoid cross-month navigation |
| 532 |
if ( |
538 |
// races (the booking modal's onMonthChange handler asynchronously |
| 533 |
boldDate.month() === clearZoneStart.month() || |
539 |
// pulls the calendar back to the start date's month). |
| 534 |
boldDate.month() === clearZoneStart.add(1, "month").month() |
540 |
cy.get("@dateTestFlatpickr").should($el => { |
| 535 |
) { |
541 |
const fp = $el[0]._flatpickr; |
|
|
542 |
const boundaryTimes = fp._loanBoundaryTimes; |
| 543 |
// Cross-frame: instanceof Set fails across iframe boundaries, |
| 544 |
// so check for Set-like interface instead |
| 545 |
expect(boundaryTimes).to.exist; |
| 546 |
expect(typeof boundaryTimes.has).to.eq("function"); |
| 547 |
|
| 548 |
expectedBoldDates.forEach(boldDate => { |
| 549 |
const ts = boldDate.toDate().getTime(); |
| 550 |
expect( |
| 551 |
boundaryTimes.has(ts), |
| 552 |
`Expected ${boldDate.format("YYYY-MM-DD")} to be a loan boundary` |
| 553 |
).to.be.true; |
| 554 |
}); |
| 555 |
}); |
| 556 |
|
| 557 |
// For dates in the currently visible month, also verify the DOM class |
| 558 |
expectedBoldDates |
| 559 |
.filter(d => d.month() === clearZoneStart.month()) |
| 560 |
.forEach(boldDate => { |
| 536 |
cy.get("@dateTestFlatpickr") |
561 |
cy.get("@dateTestFlatpickr") |
| 537 |
.getFlatpickrDate(boldDate.toDate()) |
562 |
.getFlatpickrDate(boldDate.toDate()) |
| 538 |
.should("have.class", "booking-loan-boundary"); |
563 |
.should("have.class", "booking-loan-boundary"); |
| 539 |
cy.log( |
564 |
cy.log( |
| 540 |
`✓ Day ${boldDate.format("YYYY-MM-DD")}: Has 'booking-loan-boundary' class (bold)` |
565 |
`✓ Day ${boldDate.format("YYYY-MM-DD")}: Has 'booking-loan-boundary' class (bold)` |
| 541 |
); |
566 |
); |
| 542 |
} |
567 |
}); |
| 543 |
}); |
|
|
| 544 |
|
568 |
|
| 545 |
// Verify that only expected dates are bold (have "booking-loan-boundary" class) |
569 |
// Verify that only expected dates are bold in the current view |
| 546 |
cy.get(".flatpickr-day.booking-loan-boundary").each($el => { |
570 |
cy.get(".flatpickr-day.booking-loan-boundary").each($el => { |
| 547 |
const ariaLabel = $el.attr("aria-label"); |
571 |
const ariaLabel = $el.attr("aria-label"); |
| 548 |
const date = dayjs(ariaLabel, "MMMM D, YYYY"); |
572 |
const date = dayjs(ariaLabel, "MMMM D, YYYY"); |
|
Lines 745-755
describe("Booking Modal Date Picker Tests", () => {
Link Here
|
| 745 |
// ======================================================================== |
769 |
// ======================================================================== |
| 746 |
cy.log("=== PHASE 3: Lead period conflicts ==="); |
770 |
cy.log("=== PHASE 3: Lead period conflicts ==="); |
| 747 |
|
771 |
|
| 748 |
// Hover June 11 - Lead period (June 9-10), June 9 is past |
772 |
// Hover June 14 - Lead period (June 12-13), both after today, no booking conflict |
| 749 |
// Vue version only disables when lead period has BOOKING conflicts, |
773 |
// Note: June 11 would be disabled by minimum advance booking (today+leadDays=June 12), |
| 750 |
// not when lead dates are in the past. No bookings on June 9-10. |
774 |
// so we test with June 14 which is past the minimum advance period. |
| 751 |
hoverDateByISO("2026-06-11"); |
775 |
hoverDateByISO("2026-06-14"); |
| 752 |
getDateByISO("2026-06-11").should( |
776 |
getDateByISO("2026-06-14").should( |
| 753 |
"not.have.class", |
777 |
"not.have.class", |
| 754 |
"flatpickr-disabled" |
778 |
"flatpickr-disabled" |
| 755 |
); |
779 |
); |
|
Lines 1523-1577
describe("Booking Modal Date Picker Tests", () => {
Link Here
|
| 1523 |
.should("have.class", "flatpickr-disabled"); |
1547 |
.should("have.class", "flatpickr-disabled"); |
| 1524 |
|
1548 |
|
| 1525 |
// ================================================================ |
1549 |
// ================================================================ |
| 1526 |
// SCENARIO 3: Visual feedback - booking marker dots for trail period |
1550 |
// SCENARIO 3: Visual feedback - trail period hover classes |
| 1527 |
// ================================================================ |
1551 |
// ================================================================ |
| 1528 |
cy.log( |
1552 |
cy.log( |
| 1529 |
"=== Scenario 3: Visual feedback - Trail period marker dots ===" |
1553 |
"=== Scenario 3: Visual feedback - Trail period hover classes ===" |
| 1530 |
); |
1554 |
); |
| 1531 |
|
1555 |
|
| 1532 |
// Days 13-14 should have trail marker dots |
1556 |
// Hover a date whose trail overlaps with bookings |
|
|
1557 |
// Day 13 trail should get hover-trail class on hover |
| 1533 |
cy.get("@flatpickrInput2") |
1558 |
cy.get("@flatpickrInput2") |
| 1534 |
.getFlatpickrDate(today.add(13, "day").toDate()) |
1559 |
.hoverFlatpickrDate(today.add(13, "day").toDate()); |
| 1535 |
.within(() => { |
|
|
| 1536 |
cy.get( |
| 1537 |
".booking-marker-grid .booking-marker-dot" |
| 1538 |
).should("exist"); |
| 1539 |
}); |
| 1540 |
|
| 1541 |
cy.get("@flatpickrInput2") |
1560 |
cy.get("@flatpickrInput2") |
| 1542 |
.getFlatpickrDate(today.add(14, "day").toDate()) |
1561 |
.getFlatpickrDate(today.add(13, "day").toDate()) |
| 1543 |
.within(() => { |
1562 |
.should("have.class", "booking-day--hover-trail"); |
| 1544 |
cy.get( |
|
|
| 1545 |
".booking-marker-grid .booking-marker-dot" |
| 1546 |
).should("exist"); |
| 1547 |
}); |
| 1548 |
|
1563 |
|
| 1549 |
// ================================================================ |
1564 |
// ================================================================ |
| 1550 |
// SCENARIO 4: Visual feedback - booking marker dots for lead period |
1565 |
// SCENARIO 4: Visual feedback - lead period hover classes |
| 1551 |
// ================================================================ |
1566 |
// ================================================================ |
| 1552 |
cy.log( |
1567 |
cy.log( |
| 1553 |
"=== Scenario 4: Visual feedback - Lead period marker dots ===" |
1568 |
"=== Scenario 4: Visual feedback - Lead period hover classes ===" |
| 1554 |
); |
1569 |
); |
| 1555 |
|
1570 |
|
|
|
1571 |
// Hover a date whose lead period overlaps with bookings |
| 1556 |
cy.get("@flatpickrInput2") |
1572 |
cy.get("@flatpickrInput2") |
| 1557 |
.hoverFlatpickrDate(today.add(5, "day").toDate()); |
1573 |
.hoverFlatpickrDate(today.add(8, "day").toDate()); |
| 1558 |
|
|
|
| 1559 |
// Days 8-9 should have lead marker dots |
| 1560 |
cy.get("@flatpickrInput2") |
1574 |
cy.get("@flatpickrInput2") |
| 1561 |
.getFlatpickrDate(today.add(8, "day").toDate()) |
1575 |
.getFlatpickrDate(today.add(8, "day").toDate()) |
| 1562 |
.within(() => { |
1576 |
.should("have.class", "booking-day--hover-lead"); |
| 1563 |
cy.get( |
|
|
| 1564 |
".booking-marker-grid .booking-marker-dot" |
| 1565 |
).should("exist"); |
| 1566 |
}); |
| 1567 |
|
| 1568 |
cy.get("@flatpickrInput2") |
| 1569 |
.getFlatpickrDate(today.add(9, "day").toDate()) |
| 1570 |
.within(() => { |
| 1571 |
cy.get( |
| 1572 |
".booking-marker-grid .booking-marker-dot" |
| 1573 |
).should("exist"); |
| 1574 |
}); |
| 1575 |
}); |
1577 |
}); |
| 1576 |
}); |
1578 |
}); |
| 1577 |
|
1579 |
|