import { shallowMount } from "@vue/test-utils"; import datePicker from "./date-picker"; const AVAILABLE_DATES = ["2026-01-21", "2026-01-22", "2026-01-23", "2026-01-25"]; function mountDesktop(props = {}) { Object.defineProperty(window, "innerWidth", { writable: true, configurable: true, value: 1024, }); return shallowMount(datePicker, { props: { availableDates: AVAILABLE_DATES, startDate: "2026-01-21", endDate: "2026-01-25", modelValue: null, ...props, }, }); } function mountMobile(props = {}) { Object.defineProperty(window, "innerWidth", { writable: true, configurable: true, value: 375, }); return shallowMount(datePicker, { props: { availableDates: AVAILABLE_DATES, startDate: "2026-01-21", endDate: "2026-01-25", modelValue: null, ...props, }, }); } describe("date-picker.vue", () => { afterEach(() => { Object.defineProperty(window, "innerWidth", { writable: true, configurable: true, value: 1024, }); }); describe("rendering", () => { test("renders 5 date cards on desktop", () => { const wrapper = mountDesktop(); expect(wrapper.findAll(".date-picker__day-card").length).toBe(5); wrapper.unmount(); }); test("renders 3 date cards on mobile", () => { const wrapper = mountMobile(); expect(wrapper.findAll(".date-picker__day-card").length).toBe(3); wrapper.unmount(); }); test("renders disabled cards for dates not in availableDates", () => { const wrapper = mountDesktop(); // Jan 24 is not in AVAILABLE_DATES but falls in the range Jan 21–25 const disabledCards = wrapper.findAll(".date-picker__day-card--disabled"); expect(disabledCards.length).toBeGreaterThan(0); wrapper.unmount(); }); test("back navigation button is disabled at the start", () => { const wrapper = mountDesktop(); const backBtn = wrapper.findAll(".date-picker__nav-btn")[0]; expect(backBtn.attributes("disabled")).toBeDefined(); wrapper.unmount(); }); }); describe("allDates computation", () => { test("builds a contiguous date range between first and last available date", () => { const wrapper = mountDesktop(); const allValues = wrapper.vm.allDates.map((d) => d.value); // Range Jan 21–25 should include all 5 dates expect(allValues).toEqual([ "2026-01-21", "2026-01-22", "2026-01-23", "2026-01-24", "2026-01-25", ]); wrapper.unmount(); }); test("marks dates not in availableDates as unavailable", () => { const wrapper = mountDesktop(); const jan24 = wrapper.vm.allDates.find((d) => d.value === "2026-01-24"); expect(jan24.isAvailable).toBe(false); wrapper.unmount(); }); test("marks dates in availableDates as available", () => { const wrapper = mountDesktop(); const jan21 = wrapper.vm.allDates.find((d) => d.value === "2026-01-21"); expect(jan21.isAvailable).toBe(true); wrapper.unmount(); }); test("attaches correct day abbreviation to each date", () => { const wrapper = mountDesktop(); const jan21 = wrapper.vm.allDates.find((d) => d.value === "2026-01-21"); // 2026-01-21 is a Wednesday expect(jan21.dayAbbr).toBe("WED"); wrapper.unmount(); }); test("returns empty array when availableDates is null", () => { const wrapper = mountDesktop({ availableDates: null }); expect(wrapper.vm.allDates).toEqual([]); wrapper.unmount(); }); }); describe("date selection", () => { test("emits update:modelValue with the date string when an available card is clicked", async () => { const wrapper = mountDesktop(); const availableCard = wrapper.find( ".date-picker__day-card:not(.date-picker__day-card--disabled)" ); await availableCard.trigger("click"); expect(wrapper.emitted("update:modelValue")).toBeTruthy(); expect(wrapper.emitted("update:modelValue")[0][0]).toBe("2026-01-21"); wrapper.unmount(); }); test("does not emit when a disabled card is clicked", async () => { const wrapper = mountDesktop(); const emissionCountBeforeClick = (wrapper.emitted("update:modelValue") ?? []).length; const disabledCard = wrapper.find(".date-picker__day-card--disabled"); await disabledCard.trigger("click"); expect((wrapper.emitted("update:modelValue") ?? []).length).toBe( emissionCountBeforeClick ); wrapper.unmount(); }); test("applies the selected class to the card matching modelValue", async () => { const wrapper = mountDesktop({ modelValue: "2026-01-21" }); const selectedCard = wrapper.find(".date-picker__day-card--selected"); expect(selectedCard.exists()).toBeTruthy(); wrapper.unmount(); }); test("no card has the selected class when modelValue is null", () => { const wrapper = mountDesktop({ modelValue: null }); expect(wrapper.find(".date-picker__day-card--selected").exists()).toBe(false); wrapper.unmount(); }); }); describe("navigation", () => { test("goForward advances windowStart by the window size", async () => { // Use a longer range so forward is possible on desktop (window 5) const manyDates = ["2026-01-01", "2026-01-02", "2026-01-03", "2026-01-10"]; const wrapper = mountDesktop({ availableDates: manyDates, startDate: "2026-01-01", endDate: "2026-01-10", }); expect(wrapper.vm.canGoForward).toBe(true); await wrapper.vm.goForward(); expect(wrapper.vm.windowStart).toBe(5); wrapper.unmount(); }); test("goBack decrements windowStart by the window size", async () => { const manyDates = ["2026-01-01", "2026-01-02", "2026-01-03", "2026-01-10"]; const wrapper = mountDesktop({ availableDates: manyDates, startDate: "2026-01-01", endDate: "2026-01-10", }); await wrapper.vm.goForward(); await wrapper.vm.goBack(); expect(wrapper.vm.windowStart).toBe(0); wrapper.unmount(); }); test("goBack does not go below 0", () => { const wrapper = mountDesktop(); wrapper.vm.goBack(); expect(wrapper.vm.windowStart).toBe(0); wrapper.unmount(); }); test("goForward does not exceed the last window position", async () => { // Range Jan 21–25 = 5 dates, window 5 on desktop → already at max const wrapper = mountDesktop(); await wrapper.vm.goForward(); // windowStart should not push visible window past the array length expect(wrapper.vm.windowStart + wrapper.vm.windowSize).toBeLessThanOrEqual( wrapper.vm.allDates.length + wrapper.vm.windowSize ); wrapper.unmount(); }); test("back button becomes enabled after navigating forward", async () => { const manyDates = ["2026-01-01", "2026-01-02", "2026-01-03", "2026-01-10"]; const wrapper = mountDesktop({ availableDates: manyDates, startDate: "2026-01-01", endDate: "2026-01-10", }); await wrapper.vm.goForward(); await wrapper.vm.$nextTick(); const backBtn = wrapper.findAll(".date-picker__nav-btn")[0]; expect(backBtn.attributes("disabled")).toBeUndefined(); wrapper.unmount(); }); }); describe("forward button disabled at 180-date limit", () => { function generateDateRange(startStr, count) { const dates = []; const [y, m, d] = startStr.split("-").map(Number); const cursor = new Date(y, m - 1, d); for (let i = 0; i < count; i++) { const yy = cursor.getFullYear(); const mm = String(cursor.getMonth() + 1).padStart(2, "0"); const dd = String(cursor.getDate()).padStart(2, "0"); dates.push(`${yy}-${mm}-${dd}`); cursor.setDate(cursor.getDate() + 1); } return dates; } test("canGoForward is false when allDates.length >= 180 and the last date is in the current view", async () => { const allDateValues = generateDateRange("2026-01-01", 180); const wrapper = mountDesktop({ availableDates: allDateValues, startDate: allDateValues[0], endDate: allDateValues[allDateValues.length - 1], }); // Desktop window size is 5; set windowStart to last window (index 175) wrapper.vm.windowStart = 175; await wrapper.vm.$nextTick(); expect(wrapper.vm.canGoForward).toBe(false); wrapper.unmount(); }); test("forward nav button has disabled attribute when at the end of a 180-date range", async () => { const allDateValues = generateDateRange("2026-01-01", 180); const wrapper = mountDesktop({ availableDates: allDateValues, startDate: allDateValues[0], endDate: allDateValues[allDateValues.length - 1], }); wrapper.vm.windowStart = 175; await wrapper.vm.$nextTick(); const forwardBtn = wrapper.findAll(".date-picker__nav-btn")[1]; expect(forwardBtn.attributes("disabled")).toBeDefined(); wrapper.unmount(); }); test("canGoForward is true when allDates.length >= 180 but the last date is not yet in view", () => { const allDateValues = generateDateRange("2026-01-01", 180); const wrapper = mountDesktop({ availableDates: allDateValues, startDate: allDateValues[0], endDate: allDateValues[allDateValues.length - 1], }); // windowStart = 0, first 5 of 180 dates are visible — last date is not in view expect(wrapper.vm.canGoForward).toBe(true); wrapper.unmount(); }); test("canGoForward remains true at the last window when allDates.length < 180", async () => { const allDateValues = generateDateRange("2026-01-01", 10); const wrapper = mountDesktop({ availableDates: allDateValues, startDate: allDateValues[0], endDate: allDateValues[allDateValues.length - 1], }); // windowStart = 5 puts the last 5 dates (indices 5–9) in view wrapper.vm.windowStart = 5; await wrapper.vm.$nextTick(); expect(wrapper.vm.canGoForward).toBe(true); wrapper.unmount(); }); }); describe("initial window positioning", () => { test("scrolls to the window containing the pre-selected date on mount", () => { // Jan 25 is the 5th date (index 4); with window size 5 on desktop it's still in window 0 const wrapper = mountDesktop({ modelValue: "2026-01-25" }); expect(wrapper.vm.windowStart).toBe(0); wrapper.unmount(); }); }); describe("mounted initialization", () => { test("emits first available date when mounted with no modelValue and dates present", () => { const wrapper = mountDesktop(); expect(wrapper.emitted("update:modelValue")).toBeTruthy(); expect(wrapper.emitted("update:modelValue")[0][0]).toBe("2026-01-21"); wrapper.unmount(); }); }); describe("goBack auto-selection", () => { test("emits update:modelValue with first available date in the new window", async () => { // modelValue set → mounted() uses scroll path, no emission on mount const wrapper = mountDesktop({ availableDates: ["2026-01-01", "2026-01-07"], startDate: "2026-01-01", endDate: "2026-01-10", modelValue: "2026-01-07", }); // "2026-01-07" is index 6 → mounted sets windowStart = Math.floor(6/5)*5 = 5 expect(wrapper.vm.windowStart).toBe(5); expect(wrapper.emitted("update:modelValue")).toBeFalsy(); await wrapper.vm.goBack(); const emissions = wrapper.emitted("update:modelValue"); expect(emissions).toBeTruthy(); expect(emissions[emissions.length - 1][0]).toBe("2026-01-01"); wrapper.unmount(); }); }); describe("goForward pending flow", () => { test("emits requestMoreDates when at the last window", async () => { // 5 dates, desktop window size 5 → already at end on mount const wrapper = mountDesktop({ modelValue: "2026-01-21" }); await wrapper.vm.goForward(); expect(wrapper.emitted("requestMoreDates")).toBeTruthy(); wrapper.unmount(); }); test("sets pendingAutoSelect when at the last window", async () => { const wrapper = mountDesktop({ modelValue: "2026-01-21" }); await wrapper.vm.goForward(); expect(wrapper.vm.pendingAutoSelect).toBe(true); wrapper.unmount(); }); test("emits update:modelValue with first available date in the new window when not at the end", async () => { const wrapper = mountDesktop({ availableDates: ["2026-01-01", "2026-01-07"], startDate: "2026-01-01", endDate: "2026-01-10", modelValue: "2026-01-01", }); // windowStart = 0, not at end (0 + 5 < 10), no emission on mount await wrapper.vm.goForward(); const emissions = wrapper.emitted("update:modelValue"); expect(emissions).toBeTruthy(); expect(emissions[emissions.length - 1][0]).toBe("2026-01-07"); wrapper.unmount(); }); }); describe("advanceWindowAndAutoSelect", () => { test("advances windowStart by windowSize", () => { const wrapper = mountDesktop({ availableDates: ["2026-01-01", "2026-01-07"], startDate: "2026-01-01", endDate: "2026-01-10", modelValue: "2026-01-01", }); expect(wrapper.vm.windowStart).toBe(0); wrapper.vm.advanceWindowAndAutoSelect(); expect(wrapper.vm.windowStart).toBe(5); wrapper.unmount(); }); test("caps windowStart at maxStart when already near the end", () => { const wrapper = mountDesktop({ availableDates: ["2026-01-01", "2026-01-07"], startDate: "2026-01-01", endDate: "2026-01-10", modelValue: "2026-01-07", }); // mounted() sets windowStart = 5 (index 6, Math.floor(6/5)*5) // maxStart = Math.max(0, 10 - 5) = 5 → min(5, 5+5) = 5 wrapper.vm.advanceWindowAndAutoSelect(); expect(wrapper.vm.windowStart).toBe(5); wrapper.unmount(); }); test("emits update:modelValue with first available date in the advanced window", () => { const wrapper = mountDesktop({ availableDates: ["2026-01-01", "2026-01-07"], startDate: "2026-01-01", endDate: "2026-01-10", modelValue: "2026-01-01", }); wrapper.vm.advanceWindowAndAutoSelect(); const emissions = wrapper.emitted("update:modelValue"); expect(emissions).toBeTruthy(); expect(emissions[emissions.length - 1][0]).toBe("2026-01-07"); wrapper.unmount(); }); }); describe("autoSelectFirstAvailable", () => { test("emits update:modelValue with the first available date in the visible window", () => { const wrapper = mountDesktop({ modelValue: "2026-01-21" }); wrapper.vm.autoSelectFirstAvailable(); const emissions = wrapper.emitted("update:modelValue"); expect(emissions[emissions.length - 1][0]).toBe("2026-01-21"); wrapper.unmount(); }); test("emits null when no available dates are visible", () => { // availableDates outside the date range → all visible dates are unavailable const wrapper = mountDesktop({ availableDates: ["9999-12-31"], startDate: "2026-01-21", endDate: "2026-01-25", modelValue: "2026-01-21", }); // modelValue set → mounted uses scroll path, no emission on mount wrapper.vm.autoSelectFirstAvailable(); const emissions = wrapper.emitted("update:modelValue"); expect(emissions[emissions.length - 1][0]).toBeNull(); wrapper.unmount(); }); }); describe("initializeWindow", () => { test("sets the initialized flag to true", async () => { const wrapper = mountDesktop({ availableDates: null }); expect(wrapper.vm.initialized).toBe(false); await wrapper.setProps({ availableDates: AVAILABLE_DATES }); expect(wrapper.vm.initialized).toBe(true); wrapper.unmount(); }); test("positions windowStart to the window containing the first available date", async () => { const wrapper = mountDesktop({ availableDates: null, startDate: "2026-01-01", endDate: "2026-01-10", }); // "2026-01-07" is index 6 → Math.floor(6/5)*5 = 5 await wrapper.setProps({ availableDates: ["2026-01-07"] }); expect(wrapper.vm.windowStart).toBe(5); wrapper.unmount(); }); test("emits update:modelValue with the first available date", async () => { const wrapper = mountDesktop({ availableDates: null }); await wrapper.setProps({ availableDates: AVAILABLE_DATES }); expect(wrapper.emitted("update:modelValue")[0][0]).toBe("2026-01-21"); wrapper.unmount(); }); test("positions windowStart to the last window and does not emit when no dates are available", async () => { const wrapper = mountDesktop({ availableDates: null, startDate: "2026-01-01", endDate: "2026-01-10", }); // Dates are present but none fall in the Jan 1–10 range await wrapper.setProps({ availableDates: ["9999-12-31"] }); // Math.max(0, 10 - 5) = 5 expect(wrapper.vm.windowStart).toBe(5); expect(wrapper.emitted("update:modelValue")).toBeFalsy(); wrapper.unmount(); }); test("does not re-run after initialized flag is set", async () => { const wrapper = mountDesktop({ availableDates: null }); await wrapper.setProps({ availableDates: AVAILABLE_DATES }); const emissionCount = wrapper.emitted("update:modelValue").length; // Add a newly available date within the range to trigger the watcher again await wrapper.setProps({ availableDates: [...AVAILABLE_DATES, "2026-01-24"] }); // initialized=true and pendingAutoSelect=false → watcher takes no action expect(wrapper.emitted("update:modelValue").length).toBe(emissionCount); wrapper.unmount(); }); }); describe("allDates watcher", () => { test("calls initializeWindow when dates first arrive with no modelValue set", async () => { const wrapper = mountDesktop({ availableDates: null }); // null availableDates → allDates=[] → mounted() does nothing; initialized stays false expect(wrapper.vm.initialized).toBe(false); await wrapper.setProps({ availableDates: AVAILABLE_DATES }); expect(wrapper.vm.initialized).toBe(true); expect(wrapper.emitted("update:modelValue")[0][0]).toBe("2026-01-21"); wrapper.unmount(); }); test("clears pendingAutoSelect and advances the window when pendingAutoSelect is true", async () => { const wrapper = mountDesktop({ availableDates: null, startDate: "2026-01-01", endDate: "2026-01-10", }); wrapper.vm.initialized = true; wrapper.vm.pendingAutoSelect = true; await wrapper.setProps({ availableDates: ["2026-01-01", "2026-01-07"] }); expect(wrapper.vm.pendingAutoSelect).toBe(false); expect(wrapper.vm.windowStart).toBe(5); const emissions = wrapper.emitted("update:modelValue"); expect(emissions[emissions.length - 1][0]).toBe("2026-01-07"); wrapper.unmount(); }); test("takes no action when availableDates becomes null", async () => { const wrapper = mountDesktop({ availableDates: AVAILABLE_DATES }); // mounted() calls initializeWindow() → emits; record count const emissionCount = wrapper.emitted("update:modelValue").length; await wrapper.setProps({ availableDates: null }); // watcher fires with newDates=[] → early return, nothing changes expect(wrapper.emitted("update:modelValue").length).toBe(emissionCount); wrapper.unmount(); }); }); });