DigitalConsumer.FixMyGlass/src/layouts/scheduling/date-picker/date-picker.spec.js
scottkiener-at-safelite 85e28701f3 CASH-2572 | Add in empty dates logic
It wasn't possible to display 15 days that had no actual available dates
Changed initialization logic to no longer assume an empty availableDates meant we hadn't loaded yet
Updated unit tests
Fixed a styling bug for 2571
2026-06-05 10:19:31 -04:00

519 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 2125
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 2125 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 2125 = 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 59) 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 110 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();
});
});
});