|
Lines 674-679
describe("Booking Modal Date Picker Tests", () => {
Link Here
|
| 674 |
); |
674 |
); |
| 675 |
}); |
675 |
}); |
| 676 |
|
676 |
|
|
|
677 |
it("should handle lead and trail periods", () => { |
| 678 |
/** |
| 679 |
* Lead and Trail Period Behaviour Tests |
| 680 |
* ===================================== |
| 681 |
* |
| 682 |
* Test Coverage: |
| 683 |
* 1. Lead period visual hints (CSS classes) in clear zone |
| 684 |
* 2. Trail period visual hints (CSS classes) in clear zone |
| 685 |
* 3. Lead period conflicts with past dates or existing booking (leadDisable) |
| 686 |
* 4. Trail period conflicts with existing booking (trailDisable) |
| 687 |
* 5. Max date selectable when trail period is clear of existing booking |
| 688 |
* |
| 689 |
* Fixed Date Setup: |
| 690 |
* ================ |
| 691 |
* - Today: June 10, 2026 (Wednesday) |
| 692 |
* - Timezone: Europe/London |
| 693 |
* - Lead Period: 2 days |
| 694 |
* - Trail Period: 3 days |
| 695 |
* - Issue Length: 3 days |
| 696 |
* - Renewal Period: 2 days |
| 697 |
* - Max Renewals: 2 |
| 698 |
* - Max Booking Period: 3 + (2 × 2) = 7 days |
| 699 |
* |
| 700 |
* Blocker Booking: June 25-27, 2026 |
| 701 |
* |
| 702 |
* Timeline: |
| 703 |
* ========= |
| 704 |
* June 2026 |
| 705 |
* Sun Mon Tue Wed Thu Fri Sat |
| 706 |
* 8 9 10 11 12 13 ← 10 = TODAY |
| 707 |
* 14 15 16 17 18 19 20 |
| 708 |
* 21 22 23 24 25 26 27 ← 25-27 = BLOCKER |
| 709 |
* 28 29 30 |
| 710 |
* |
| 711 |
* Test Scenarios: |
| 712 |
* ============== |
| 713 |
* Phase 1: Hover June 13 → Lead June 11-12 (clear) → no leadDisable |
| 714 |
* Phase 2: Select June 13, hover June 16 → Trail June 17-19 (clear) → no trailDisable |
| 715 |
* Phase 3a: Hover June 11 → Lead June 9-10, June 9 is past → leadDisable |
| 716 |
* Phase 3b: Hover June 29 → Lead June 27-28, June 27 is in blocker → leadDisable |
| 717 |
* Phase 4: Select June 20, hover June 23 → Trail June 24-26 overlaps blocker → trailDisable |
| 718 |
* Phase 5: Select June 13, hover June 20 (max) → Trail June 21-23 (clear) → selectable |
| 719 |
*/ |
| 720 |
|
| 721 |
// Fix the browser Date object to June 10, 2026 at 09:00 Europe/London |
| 722 |
// Using ["Date"] to avoid freezing timers which breaks Select2 async operations |
| 723 |
const fixedToday = new Date("2026-06-10T08:00:00Z"); // 09:00 BST (UTC+1) |
| 724 |
cy.clock(fixedToday, ["Date"]); |
| 725 |
cy.log(`Fixed today: June 10, 2026`); |
| 726 |
|
| 727 |
// Circulation rules with short periods for focused testing |
| 728 |
const circulationRules = { |
| 729 |
bookings_lead_period: 2, |
| 730 |
bookings_trail_period: 3, |
| 731 |
issuelength: 3, |
| 732 |
renewalsallowed: 2, |
| 733 |
renewalperiod: 2, |
| 734 |
}; |
| 735 |
|
| 736 |
const maxBookingPeriod = |
| 737 |
circulationRules.issuelength + |
| 738 |
circulationRules.renewalsallowed * circulationRules.renewalperiod; // 7 days |
| 739 |
cy.log(`Max booking period: ${maxBookingPeriod} days`); |
| 740 |
|
| 741 |
cy.intercept("GET", "/api/v1/circulation_rules*", { |
| 742 |
body: [circulationRules], |
| 743 |
}).as("getFixedDateRules"); |
| 744 |
|
| 745 |
// Create blocker booking: June 25-27, 2026 |
| 746 |
const blockerStart = "2026-06-25 00:00:00"; |
| 747 |
const blockerEnd = "2026-06-27 23:59:59"; |
| 748 |
|
| 749 |
cy.task("query", { |
| 750 |
sql: `INSERT INTO bookings (biblio_id, item_id, patron_id, start_date, end_date, pickup_library_id, status) |
| 751 |
VALUES (?, ?, ?, ?, ?, ?, '1')`, |
| 752 |
values: [ |
| 753 |
testData.biblio.biblio_id, |
| 754 |
testData.items[0].item_id, |
| 755 |
testData.patron.patron_id, |
| 756 |
blockerStart, |
| 757 |
blockerEnd, |
| 758 |
testData.libraries[0].library_id, |
| 759 |
], |
| 760 |
}); |
| 761 |
cy.log(`Blocker booking created: June 25-27, 2026`); |
| 762 |
|
| 763 |
// Setup modal |
| 764 |
setupModalForDateTesting({ skipItemSelection: true }); |
| 765 |
|
| 766 |
cy.get("#booking_item_id").should("not.be.disabled"); |
| 767 |
cy.selectFromSelect2ByIndex("#booking_item_id", 1); |
| 768 |
cy.wait("@getFixedDateRules"); |
| 769 |
|
| 770 |
cy.get("#period").should("not.be.disabled"); |
| 771 |
cy.get("#period").as("fp"); |
| 772 |
cy.get("@fp").openFlatpickr(); |
| 773 |
|
| 774 |
// Helper to get a specific date element by ISO date string |
| 775 |
const getDateByISO = (isoDate: string) => { |
| 776 |
const date = new Date(isoDate); |
| 777 |
return cy.get("@fp").getFlatpickrDate(date); |
| 778 |
}; |
| 779 |
|
| 780 |
// ======================================================================== |
| 781 |
// PHASE 1: Lead Period Clear - Visual Classes |
| 782 |
// ======================================================================== |
| 783 |
cy.log("=== PHASE 1: Lead period visual hints in clear zone ==="); |
| 784 |
|
| 785 |
/** |
| 786 |
* Hover June 13 as potential start date |
| 787 |
* Lead period: June 11-12 (both after today June 10, no booking conflict) |
| 788 |
* Expected: leadRangeStart on June 11, leadRange on June 12, no leadDisable on June 13 |
| 789 |
*/ |
| 790 |
|
| 791 |
getDateByISO("2026-06-13").trigger("mouseover"); |
| 792 |
|
| 793 |
// Check lead period classes |
| 794 |
getDateByISO("2026-06-11") |
| 795 |
.should("have.class", "leadRangeStart") |
| 796 |
.and("have.class", "leadRange"); |
| 797 |
cy.log("✓ June 11: Has leadRange and leadRangeStart classes"); |
| 798 |
|
| 799 |
getDateByISO("2026-06-12").should("have.class", "leadRange"); |
| 800 |
cy.log("✓ June 12: Has leadRange class"); |
| 801 |
|
| 802 |
// Hovered date should NOT have leadDisable (lead period is clear) |
| 803 |
getDateByISO("2026-06-13") |
| 804 |
.should("have.class", "leadRangeEnd") |
| 805 |
.and("not.have.class", "leadDisable"); |
| 806 |
cy.log( |
| 807 |
"✓ June 13: Has leadRangeEnd and not leadDisable (lead period is clear)" |
| 808 |
); |
| 809 |
|
| 810 |
// ======================================================================== |
| 811 |
// PHASE 2: Trail Period Clear - Visual Classes |
| 812 |
// ======================================================================== |
| 813 |
cy.log("=== PHASE 2: Trail period visual hints in clear zone ==="); |
| 814 |
|
| 815 |
/** |
| 816 |
* Select June 13 as start date (lead June 11-12 is clear) |
| 817 |
* Then hover June 16 as potential end date |
| 818 |
* Trail period calculation: trailStart = hoverDate, trailEnd = hoverDate + 3 |
| 819 |
* So: trailStart = June 16, trailEnd = June 19 |
| 820 |
* Classes: June 16 = trailRangeStart, June 17-18 = trailRange, June 19 = trailRange + trailRangeEnd |
| 821 |
*/ |
| 822 |
|
| 823 |
// Select June 13 as start date (same date we just hovered - lead is clear) |
| 824 |
getDateByISO("2026-06-13").click(); |
| 825 |
cy.log("Selected June 13 as start date"); |
| 826 |
|
| 827 |
// Hover June 16 as potential end date |
| 828 |
getDateByISO("2026-06-16").trigger("mouseover"); |
| 829 |
|
| 830 |
// Check trail period classes |
| 831 |
// trailRangeStart is on the hovered date itself (June 16) |
| 832 |
getDateByISO("2026-06-16").should("have.class", "trailRangeStart"); |
| 833 |
cy.log("✓ June 16: Has trailRangeStart class (hovered date)"); |
| 834 |
|
| 835 |
// trailRange is on days after trailStart up to and including trailEnd |
| 836 |
getDateByISO("2026-06-17").should("have.class", "trailRange"); |
| 837 |
cy.log("✓ June 17: Has trailRange class"); |
| 838 |
|
| 839 |
getDateByISO("2026-06-18").should("have.class", "trailRange"); |
| 840 |
cy.log("✓ June 18: Has trailRange class"); |
| 841 |
|
| 842 |
// trailRangeEnd is on the last day of trail period |
| 843 |
getDateByISO("2026-06-19") |
| 844 |
.should("have.class", "trailRangeEnd") |
| 845 |
.and("have.class", "trailRange"); |
| 846 |
cy.log("✓ June 19: Has trailRangeEnd and trailRange classes"); |
| 847 |
|
| 848 |
// Hovered date should NOT have trailDisable (trail period is clear) |
| 849 |
getDateByISO("2026-06-16").should("not.have.class", "trailDisable"); |
| 850 |
cy.log("✓ June 16: No trailDisable (trail period is clear)"); |
| 851 |
|
| 852 |
// Clear selection for next phase |
| 853 |
cy.get("#period").clearFlatpickr(); |
| 854 |
cy.get("@fp").openFlatpickr(); |
| 855 |
cy.log("Cleared selection for next phase"); |
| 856 |
|
| 857 |
// ======================================================================== |
| 858 |
// PHASE 3: Lead Period Conflict - Past Dates and Existing bookings |
| 859 |
// ======================================================================== |
| 860 |
cy.log("=== PHASE 3: Lead period conflicts ==="); |
| 861 |
|
| 862 |
/** |
| 863 |
* Hover June 11 as potential start date |
| 864 |
* Lead period: June 9-10 |
| 865 |
* June 9 is in the past (before today June 10) |
| 866 |
* Expected: leadDisable on June 11 because lead period extends into past |
| 867 |
*/ |
| 868 |
|
| 869 |
getDateByISO("2026-06-11").trigger("mouseover"); |
| 870 |
|
| 871 |
// June 11 should have leadDisable because lead period (June 9-10) includes past date |
| 872 |
getDateByISO("2026-06-11").should("have.class", "leadDisable"); |
| 873 |
cy.log( |
| 874 |
"✓ June 11: Has leadDisable (lead period June 9-10 includes past date)" |
| 875 |
); |
| 876 |
|
| 877 |
/** |
| 878 |
* Hover June 29 as potential start date |
| 879 |
* Lead period: June 27-28 |
| 880 |
* June 27 is in the existing booking (25-27 June) |
| 881 |
* Expected: leadDisable on June 29 because lead period extends into existing booking |
| 882 |
*/ |
| 883 |
|
| 884 |
getDateByISO("2026-06-29").trigger("mouseover"); |
| 885 |
|
| 886 |
// June 29 should have leadDisable because lead period (June 27-28) includes existing booking date |
| 887 |
getDateByISO("2026-06-29").should("have.class", "leadDisable"); |
| 888 |
cy.log( |
| 889 |
"✓ June 29: Has leadDisable (lead period June 27-28 includes existing booking date)" |
| 890 |
); |
| 891 |
|
| 892 |
// ======================================================================== |
| 893 |
// PHASE 4: Trail Period Conflict - Existing Booking |
| 894 |
// ======================================================================== |
| 895 |
cy.log("=== PHASE 4: Trail period conflict with existing booking ==="); |
| 896 |
|
| 897 |
/** |
| 898 |
* Select June 20 as start date (lead June 18-19, both clear) |
| 899 |
* Then hover June 23 as potential end date |
| 900 |
* Trail period: June 24-26 |
| 901 |
* Blocker booking: June 25-27 (partial overlap) |
| 902 |
* Expected: trailDisable on June 23 |
| 903 |
*/ |
| 904 |
|
| 905 |
// Select June 20 as start date |
| 906 |
getDateByISO("2026-06-20").click(); |
| 907 |
cy.log("Selected June 20 as start date"); |
| 908 |
|
| 909 |
// Hover June 23 as potential end date |
| 910 |
getDateByISO("2026-06-23").trigger("mouseover"); |
| 911 |
|
| 912 |
// June 23 should have trailDisable because trail period (June 24-26) overlaps blocker (June 25-27) |
| 913 |
getDateByISO("2026-06-23").should("have.class", "trailDisable"); |
| 914 |
cy.log( |
| 915 |
"✓ June 23: Has trailDisable (trail June 24-26 overlaps blocker June 25-27)" |
| 916 |
); |
| 917 |
|
| 918 |
// Clear selection for next phase |
| 919 |
cy.get("#period").clearFlatpickr(); |
| 920 |
cy.get("@fp").openFlatpickr(); |
| 921 |
cy.log("Cleared selection for next phase"); |
| 922 |
|
| 923 |
// ======================================================================== |
| 924 |
// PHASE 5: Max Date Selectable When Trail is Clear |
| 925 |
// ======================================================================== |
| 926 |
cy.log("=== PHASE 5: Max date selectable when trail is clear ==="); |
| 927 |
|
| 928 |
/** |
| 929 |
* Select June 13 as start date (lead June 11-12, both clear) |
| 930 |
* Max end date: June 20 (13 + 7 days) |
| 931 |
* Hover June 20: Trail period June 21-23 |
| 932 |
* Trail period is clear (blocker is June 25-27) |
| 933 |
* Expected: June 20 is selectable (no trailDisable), can book full 7-day period |
| 934 |
*/ |
| 935 |
|
| 936 |
// Select June 13 as start date |
| 937 |
getDateByISO("2026-06-13").click(); |
| 938 |
cy.log("Selected June 13 as start date"); |
| 939 |
|
| 940 |
// Hover June 20 (max date = start + 7 days) |
| 941 |
getDateByISO("2026-06-20").trigger("mouseover"); |
| 942 |
|
| 943 |
// Max date should NOT have trailDisable (trail June 21-23 is clear) |
| 944 |
getDateByISO("2026-06-20").should("not.have.class", "trailDisable"); |
| 945 |
cy.log("✓ June 20: No trailDisable (trail June 21-23 is clear)"); |
| 946 |
|
| 947 |
// Max date should not be disabled by flatpickr |
| 948 |
getDateByISO("2026-06-20").should( |
| 949 |
"not.have.class", |
| 950 |
"flatpickr-disabled" |
| 951 |
); |
| 952 |
cy.log("✓ June 20: Not flatpickr-disabled (max date is selectable)"); |
| 953 |
|
| 954 |
// Actually select the max date to confirm booking can be made |
| 955 |
getDateByISO("2026-06-20").click(); |
| 956 |
|
| 957 |
// Verify dates were accepted in the form |
| 958 |
cy.get("#booking_start_date").should("not.have.value", ""); |
| 959 |
cy.get("#booking_end_date").should("not.have.value", ""); |
| 960 |
cy.log("✓ Full 7-day period selected: June 13 to June 20"); |
| 961 |
|
| 962 |
// ======================================================================== |
| 963 |
// SUMMARY |
| 964 |
// ======================================================================== |
| 965 |
cy.log( |
| 966 |
"✓ CONFIRMED: Lead and trail period behaviour working correctly" |
| 967 |
); |
| 968 |
cy.log("✓ Phase 1: Lead period visual hints appear in clear zones"); |
| 969 |
cy.log("✓ Phase 2: Trail period visual hints appear in clear zones"); |
| 970 |
cy.log( |
| 971 |
"✓ Phase 3: Lead period into past dates or existing bookings triggers leadDisable" |
| 972 |
); |
| 973 |
cy.log( |
| 974 |
"✓ Phase 4: Trail period overlapping booking triggers trailDisable" |
| 975 |
); |
| 976 |
cy.log( |
| 977 |
"✓ Phase 5: Max date is selectable when trail period has no conflicts" |
| 978 |
); |
| 979 |
}); |
| 980 |
|
| 677 |
it("should show event dots for dates with existing bookings", () => { |
981 |
it("should show event dots for dates with existing bookings", () => { |
| 678 |
/** |
982 |
/** |
| 679 |
* Comprehensive Event Dots Visual Indicator Test |
983 |
* Comprehensive Event Dots Visual Indicator Test |
| 680 |
- |
|
|