This is an automated email from the ASF dual-hosted git repository. wusheng pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/skywalking-booster-ui.git
The following commit(s) were added to refs/heads/main by this push: new f069c8a0 feat: optimize the time picker component (#494) f069c8a0 is described below commit f069c8a081543f78a078ca8db477081c21c2e129 Author: Fine0830 <fanxue0...@gmail.com> AuthorDate: Wed Aug 27 21:20:53 2025 +0800 feat: optimize the time picker component (#494) --- src/components/DateCalendar.vue | 16 +- src/components/TimePicker.vue | 120 ++- src/components/__tests__/DateCalendar.spec.ts | 836 +++++++++++++-------- src/components/__tests__/TimePicker.spec.ts | 462 ++++++------ src/hooks/useExpressionsProcessor.ts | 1 - src/layout/components/NavBar.vue | 3 +- .../related/profile/components/NewTask.vue | 2 +- stylelint.config.js | 3 - 8 files changed, 829 insertions(+), 614 deletions(-) diff --git a/src/components/DateCalendar.vue b/src/components/DateCalendar.vue index 293e80a3..fc92d4d1 100755 --- a/src/components/DateCalendar.vue +++ b/src/components/DateCalendar.vue @@ -166,11 +166,10 @@ limitations under the License. --> const emit = defineEmits(["input", "setDates", "ok"]); const { t } = useI18n(); const props = defineProps({ - value: { type: Date }, + value: { type: Object as PropType<Date>, default: () => new Date() }, left: { type: Boolean, default: false }, right: { type: Boolean, default: false }, dates: { type: Array as PropType<Date[]>, default: () => [] }, - disabledDate: { type: Function, default: () => false }, format: { type: String, default: "YYYY-MM-DD", @@ -246,7 +245,7 @@ limitations under the License. --> return parse(Number(props.maxRange[0])); }); const maxEnd = computed(() => { - return parse(Number(props.maxRange[1])); + return parse(Number(props.maxRange[1]) + 23 * 60 * 60 * 1000); }); const ys = computed(() => { return Math.floor(state.year / 10) * 10; @@ -376,10 +375,13 @@ limitations under the License. --> flag = tf(props.value, format) === tf(time, format); } classObj[`${state.pre}-date`] = true; - const rightDisabled = props.right && (t < start.value || t > maxEnd.value || !props.maxRange?.length); - const leftDisabled = - props.left && (t < minStart.value || t > end.value || !props.maxRange?.length || t > maxEnd.value); - classObj[`${state.pre}-date-disabled`] = rightDisabled || leftDisabled || props.disabledDate(time, format); + + // Only apply range constraints when maxRange is provided and has valid dates + const hasMaxRange = props.maxRange && props.maxRange.length === 2; + const rightDisabled = props.right && hasMaxRange && (t < start.value || t > maxEnd.value); + const leftDisabled = props.left && hasMaxRange && (t < minStart.value || t > end.value || t > maxEnd.value); + + classObj[`${state.pre}-date-disabled`] = rightDisabled || leftDisabled; classObj[`${state.pre}-date-on`] = (props.left && t > start.value) || (props.right && t < end.value); classObj[`${state.pre}-date-selected`] = flag; return classObj; diff --git a/src/components/TimePicker.vue b/src/components/TimePicker.vue index 75559d26..422d0644 100755 --- a/src/components/TimePicker.vue +++ b/src/components/TimePicker.vue @@ -41,22 +41,52 @@ limitations under the License. --> > <template v-if="range"> <div class="datepicker-popup__sidebar"> - <button type="button" class="datepicker-popup__shortcut" @click="quickPick('quarter')"> + <button + type="button" + class="datepicker-popup__shortcut" + :class="{ 'datepicker-popup__shortcut--selected': selectedShortcut === QUICK_PICK_TYPES.QUARTER }" + @click="quickPick(QUICK_PICK_TYPES.QUARTER)" + > {{ local.quarterHourCutTip }} </button> - <button type="button" class="datepicker-popup__shortcut" @click="quickPick('half')"> + <button + type="button" + class="datepicker-popup__shortcut" + :class="{ 'datepicker-popup__shortcut--selected': selectedShortcut === QUICK_PICK_TYPES.HALF }" + @click="quickPick(QUICK_PICK_TYPES.HALF)" + > {{ local.halfHourCutTip }} </button> - <button type="button" class="datepicker-popup__shortcut" @click="quickPick('hour')"> + <button + type="button" + class="datepicker-popup__shortcut" + :class="{ 'datepicker-popup__shortcut--selected': selectedShortcut === QUICK_PICK_TYPES.HOUR }" + @click="quickPick(QUICK_PICK_TYPES.HOUR)" + > {{ local.hourCutTip }} </button> - <button type="button" class="datepicker-popup__shortcut" @click="quickPick('day')"> + <button + type="button" + class="datepicker-popup__shortcut" + :class="{ 'datepicker-popup__shortcut--selected': selectedShortcut === QUICK_PICK_TYPES.DAY }" + @click="quickPick(QUICK_PICK_TYPES.DAY)" + > {{ local.dayCutTip }} </button> - <button type="button" class="datepicker-popup__shortcut" @click="quickPick('week')"> + <button + type="button" + class="datepicker-popup__shortcut" + :class="{ 'datepicker-popup__shortcut--selected': selectedShortcut === QUICK_PICK_TYPES.WEEK }" + @click="quickPick(QUICK_PICK_TYPES.WEEK)" + > {{ local.weekCutTip }} </button> - <button type="button" class="datepicker-popup__shortcut" @click="quickPick('month')"> + <button + type="button" + class="datepicker-popup__shortcut" + :class="{ 'datepicker-popup__shortcut--selected': selectedShortcut === QUICK_PICK_TYPES.MONTH }" + @click="quickPick(QUICK_PICK_TYPES.MONTH)" + > {{ local.monthCutTip }} </button> </div> @@ -66,7 +96,6 @@ limitations under the License. --> :value="dates[0]" :dates="dates" :left="true" - :disabledDate="disabledDate" :format="format" :maxRange="maxRange" @ok="ok" @@ -77,7 +106,6 @@ limitations under the License. --> :value="dates[1]" :dates="dates" :right="true" - :disabledDate="disabledDate" :format="format" :maxRange="maxRange" @ok="ok" @@ -89,7 +117,6 @@ limitations under the License. --> <DateCalendar v-model="dates[0]" :value="dates[0]" - :disabledDate="disabledDate" :dates="dates" :format="format" @ok="ok" @@ -110,15 +137,29 @@ limitations under the License. --> </template> <script lang="ts" setup> - import { ref, computed, onMounted, onBeforeUnmount, watch } from "vue"; + import { ref, computed, onMounted, onBeforeUnmount, watch, PropType } from "vue"; import { useI18n } from "vue-i18n"; import DateCalendar from "./DateCalendar.vue"; import { useTimeoutFn } from "@/hooks/useTimeout"; - /*global PropType, defineProps, defineEmits*/ + /* global defineProps, defineEmits */ + + const QUICK_PICK_TYPES = { + QUARTER: "quarter", + HALF: "half", + HOUR: "hour", + DAY: "day", + WEEK: "week", + MONTH: "month", + } as const; + + type QuickPickType = typeof QUICK_PICK_TYPES[keyof typeof QUICK_PICK_TYPES]; + const datepicker = ref(null); const { t } = useI18n(); const show = ref<boolean>(false); const dates = ref<Date[]>([]); + const inputDates = ref<Date[]>([]); + const selectedShortcut = ref<string>(QUICK_PICK_TYPES.HALF); const props = defineProps({ position: { type: String, default: "bottom" }, name: [String], @@ -139,10 +180,6 @@ limitations under the License. --> default: false, }, placeholder: [String], - disabledDate: { - type: Function, - default: () => false, - }, format: { type: String, default: "YYYY-MM-DD", @@ -208,15 +245,15 @@ limitations under the License. --> return dates.value.length === 2; }); const text = computed(() => { - const val = props.value; - const txt = dates.value.map((date: Date) => tf(date)).join(` ${props.rangeSeparator} `); - if (Array.isArray(val)) { - return val.length > 1 ? txt : ""; + const txt = inputDates.value.map((date: Date) => tf(date)).join(` ${props.rangeSeparator} `); + if (Array.isArray(props.value)) { + return props.value.length > 1 ? txt : ""; } - return val ? txt : ""; + return props.value ? txt : ""; }); const get = () => { - return Array.isArray(props.value) ? dates.value : dates.value[0]; + const currentDates = props.showButtons ? inputDates.value : dates.value; + return Array.isArray(props.value) ? currentDates : currentDates[0]; }; const cls = () => { emit("clear"); @@ -224,7 +261,7 @@ limitations under the License. --> }; const vi = (val: any) => { if (Array.isArray(val)) { - return val.length > 1 ? val.map((item) => new Date(item)) : [new Date(), new Date()]; + return val.length >= 1 ? val.map((item) => new Date(item)) : [new Date(), new Date()]; } return val ? [new Date(val)] : [new Date()]; }; @@ -246,44 +283,47 @@ limitations under the License. --> const dc = (e: MouseEvent) => { show.value = (datepicker.value as any).contains(e.target) && !props.disabled; }; - const quickPick = (type: string) => { + const quickPick = (type: QuickPickType) => { const end = new Date(); const start = new Date(); + selectedShortcut.value = type; switch (type) { - case "quarter": + case QUICK_PICK_TYPES.QUARTER: start.setTime(start.getTime() - 60 * 15 * 1000); //15 mins break; - case "half": + case QUICK_PICK_TYPES.HALF: start.setTime(start.getTime() - 60 * 30 * 1000); //30 mins break; - case "hour": + case QUICK_PICK_TYPES.HOUR: start.setTime(start.getTime() - 3600 * 1000); //1 hour break; - case "day": + case QUICK_PICK_TYPES.DAY: start.setTime(start.getTime() - 3600 * 1000 * 24); //1 day break; - case "week": + case QUICK_PICK_TYPES.WEEK: start.setTime(start.getTime() - 3600 * 1000 * 24 * 7); //1 week break; - case "month": + case QUICK_PICK_TYPES.MONTH: start.setTime(start.getTime() - 3600 * 1000 * 24 * 30); //1 month break; default: break; } dates.value = [start, end]; - emit("input", get()); }; const submit = () => { + inputDates.value = dates.value; emit("confirm", get()); show.value = false; }; const cancel = () => { emit("cancel"); show.value = false; + dates.value = vi(props.value); }; onMounted(() => { dates.value = vi(props.value); + inputDates.value = dates.value; document.addEventListener("click", dc, true); }); onBeforeUnmount(() => { @@ -293,6 +333,7 @@ limitations under the License. --> () => props.value, (val: unknown) => { dates.value = vi(val); + inputDates.value = [...dates.value]; }, ); </script> @@ -462,11 +503,15 @@ limitations under the License. --> color: var(--sw-topology-color); text-align: left; outline: none; - cursor: pointer; white-space: nowrap; &:hover { - color: #3f97e3; + color: var(--el-color-primary); + cursor: pointer; + } + + &--selected { + color: var(--el-color-primary); } } @@ -520,20 +565,21 @@ limitations under the License. --> } .datepicker__buttons button { - display: inline-block; - font-size: 13px; border: none; cursor: pointer; - margin: 10px 0 0 5px; - padding: 5px 15px; color: $text-color; + margin-left: 5px; + padding: 2px 5px; } .datepicker__buttons .datepicker__button-select { - background: #3f97e3; + background: var(--el-color-primary); + border-radius: 2px; + color: #fff; } .datepicker__buttons .datepicker__button-cancel { background: var(--sw-topology-color); + border-radius: 2px; } </style> diff --git a/src/components/__tests__/DateCalendar.spec.ts b/src/components/__tests__/DateCalendar.spec.ts index 1e9b754e..817ac8d0 100644 --- a/src/components/__tests__/DateCalendar.spec.ts +++ b/src/components/__tests__/DateCalendar.spec.ts @@ -15,8 +15,8 @@ * limitations under the License. */ -import { describe, it, expect, vi, beforeEach } from "vitest"; import { mount } from "@vue/test-utils"; +import { describe, it, expect, vi } from "vitest"; import { nextTick } from "vue"; import DateCalendar from "../DateCalendar.vue"; @@ -25,10 +25,10 @@ vi.mock("vue-i18n", () => ({ useI18n: () => ({ t: (key: string) => { const translations: Record<string, string> = { - hourTip: "Select Hour", - minuteTip: "Select Minute", - secondTip: "Select Second", - yearSuffix: "", + hourTip: "Hour", + minuteTip: "Minute", + secondTip: "Second", + yearSuffix: "Year", monthsHead: "January_February_March_April_May_June_July_August_September_October_November_December", months: "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec", weeks: "Mon_Tue_Wed_Thu_Fri_Sat_Sun", @@ -47,42 +47,24 @@ vi.mock("vue-i18n", () => ({ })); describe("DateCalendar Component", () => { - let wrapper: Recordable; - - const mockDate = new Date(2024, 0, 15, 10, 30, 45); // January 15, 2024, 10:30:45 - - beforeEach(() => { - vi.clearAllMocks(); - }); + let wrapper: any; + const mockDate = new Date(2024, 0, 15, 10, 30, 45); + const mockDateRange = [new Date(2024, 0, 10), new Date(2024, 0, 20)]; describe("Props", () => { it("should render with default props", () => { wrapper = mount(DateCalendar); - expect(wrapper.exists()).toBe(true); - // When no value is provided, state.pre is empty initially - expect(wrapper.vm.state.pre).toBe(""); - expect(wrapper.vm.state.m).toBe("D"); - expect(wrapper.vm.state.showYears).toBe(false); - expect(wrapper.vm.state.showMonths).toBe(false); - expect(wrapper.vm.state.showHours).toBe(false); - expect(wrapper.vm.state.showMinutes).toBe(false); - expect(wrapper.vm.state.showSeconds).toBe(false); + expect(wrapper.classes()).toContain("calendar"); }); - it("should render with custom value", () => { + it("should render with value prop", () => { wrapper = mount(DateCalendar, { props: { value: mockDate, }, }); - - expect(wrapper.vm.state.year).toBe(2024); - expect(wrapper.vm.state.month).toBe(0); // January is 0 - expect(wrapper.vm.state.day).toBe(15); - expect(wrapper.vm.state.hour).toBe(10); - expect(wrapper.vm.state.minute).toBe(30); - expect(wrapper.vm.state.second).toBe(45); + expect(wrapper.vm.value).toEqual(mockDate); }); it("should render with left prop", () => { @@ -91,8 +73,7 @@ describe("DateCalendar Component", () => { left: true, }, }); - - expect(wrapper.props("left")).toBe(true); + expect(wrapper.vm.left).toBe(true); }); it("should render with right prop", () => { @@ -101,327 +82,316 @@ describe("DateCalendar Component", () => { right: true, }, }); - - expect(wrapper.props("right")).toBe(true); + expect(wrapper.vm.right).toBe(true); }); - it("should render with custom format", () => { + it("should render with dates array", () => { wrapper = mount(DateCalendar, { props: { - format: "YYYY-MM-DD HH:mm:ss", + dates: mockDateRange, }, }); - - expect(wrapper.props("format")).toBe("YYYY-MM-DD HH:mm:ss"); + expect(wrapper.vm.dates).toEqual(mockDateRange); }); - it("should render with dates array", () => { - const dates = [new Date(2024, 0, 1), new Date(2024, 0, 31)]; + it("should render with custom format", () => { wrapper = mount(DateCalendar, { props: { - dates, + format: "YYYY-MM-DD HH:mm:ss", }, }); - - expect(wrapper.props("dates")).toEqual(dates); + expect(wrapper.vm.format).toBe("YYYY-MM-DD HH:mm:ss"); }); it("should render with maxRange array", () => { - const maxRange = [new Date(2024, 0, 1), new Date(2024, 11, 31)]; wrapper = mount(DateCalendar, { props: { - maxRange, + maxRange: [new Date(2024, 0, 1), new Date(2024, 0, 31)], }, }); - - expect(wrapper.props("maxRange")).toEqual(maxRange); + expect(wrapper.vm.maxRange).toHaveLength(2); }); + }); - it("should render with disabledDate function", () => { - const disabledDate = vi.fn(() => false); + describe("Computed Properties", () => { + it("should calculate start date correctly", () => { wrapper = mount(DateCalendar, { props: { - disabledDate, + dates: mockDateRange, }, }); - - expect(wrapper.props("disabledDate")).toBe(disabledDate); + expect(wrapper.vm.start).toBeDefined(); }); - }); - describe("Computed Properties", () => { - beforeEach(() => { + it("should calculate end date correctly", () => { wrapper = mount(DateCalendar, { props: { - value: mockDate, + dates: mockDateRange, }, }); + expect(wrapper.vm.end).toBeDefined(); }); - it("should calculate start date correctly", () => { - const dates = [new Date(2024, 0, 1), new Date(2024, 0, 31)]; + it("should calculate minStart correctly", () => { wrapper = mount(DateCalendar, { props: { - dates, + maxRange: [new Date(2024, 0, 1), new Date(2024, 0, 31)], }, }); - - // The actual value depends on the parse function implementation - expect(wrapper.vm.start).toBeGreaterThan(0); + expect(wrapper.vm.minStart).toBeDefined(); }); - it("should calculate end date correctly", () => { - const dates = [new Date(2024, 0, 1), new Date(2024, 0, 31)]; + it("should calculate maxEnd correctly", () => { wrapper = mount(DateCalendar, { props: { - dates, + maxRange: [new Date(2024, 0, 1), new Date(2024, 0, 31)], }, }); - - // The actual value depends on the parse function implementation - expect(wrapper.vm.end).toBeGreaterThan(0); + expect(wrapper.vm.maxEnd).toBeDefined(); }); it("should calculate year start correctly", () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); expect(wrapper.vm.ys).toBe(2020); }); it("should calculate year end correctly", () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); expect(wrapper.vm.ye).toBe(2030); }); - it("should generate years array correctly", () => { - const years = wrapper.vm.years; - expect(years).toHaveLength(12); - // The years array should have 12 consecutive years - expect(years[11] - years[0]).toBe(11); - expect(years[0]).toBeGreaterThan(0); - expect(years[11]).toBeGreaterThan(0); - }); - - it("should generate days array correctly", () => { - const days = wrapper.vm.days; - expect(days).toHaveLength(42); // 6 weeks * 7 days - - // Check that we have the correct number of days for January 2024 - const currentMonthDays = days.filter((day: Recordable) => !day.p && !day.n); - expect(currentMonthDays).toHaveLength(31); + it("should calculate years array correctly", () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); + expect(wrapper.vm.years).toHaveLength(12); }); - it("should format time correctly with dd function", () => { - expect(wrapper.vm.dd(5)).toBe("05"); - expect(wrapper.vm.dd(10)).toBe("10"); - expect(wrapper.vm.dd(0)).toBe("00"); + it("should calculate days array correctly", () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); + expect(wrapper.vm.days).toHaveLength(42); }); - }); - describe("Navigation", () => { - beforeEach(() => { + it("should calculate local translations correctly", () => { wrapper = mount(DateCalendar, { props: { value: mockDate, }, }); + expect(wrapper.vm.local.monthsHead).toHaveLength(12); + expect(wrapper.vm.local.months).toHaveLength(12); + expect(wrapper.vm.local.weeks).toHaveLength(7); }); + }); - it("should navigate to next month", async () => { - const initialMonth = wrapper.vm.state.month; - const initialYear = wrapper.vm.state.year; - - await wrapper.vm.nm(); - await nextTick(); - - if (initialMonth === 11) { - expect(wrapper.vm.state.month).toBe(0); - expect(wrapper.vm.state.year).toBe(initialYear + 1); - } else { - expect(wrapper.vm.state.month).toBe(initialMonth + 1); - expect(wrapper.vm.state.year).toBe(initialYear); - } + describe("Methods", () => { + it("should parse numbers correctly", () => { + wrapper = mount(DateCalendar); + const result = wrapper.vm.parse(100000); + expect(result).toBe(1); }); - it("should navigate to previous month", async () => { - const initialMonth = wrapper.vm.state.month; - const initialYear = wrapper.vm.state.year; + it("should handle next month navigation", () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); + const originalMonth = wrapper.vm.state.month; + wrapper.vm.nm(); + expect(wrapper.vm.state.month).toBe(originalMonth + 1); + }); - await wrapper.vm.pm(); - await nextTick(); + it("should handle previous month navigation", () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); + const originalMonth = wrapper.vm.state.month; + const originalYear = wrapper.vm.state.year; + wrapper.vm.pm(); - if (initialMonth === 0) { + // Handle month wrapping: if originalMonth was 0 (January), it should wrap to 11 (December) + if (originalMonth === 0) { expect(wrapper.vm.state.month).toBe(11); - expect(wrapper.vm.state.year).toBe(initialYear - 1); + expect(wrapper.vm.state.year).toBe(originalYear - 1); // Year should be decremented } else { - expect(wrapper.vm.state.month).toBe(initialMonth - 1); - expect(wrapper.vm.state.year).toBe(initialYear); + expect(wrapper.vm.state.month).toBe(originalMonth - 1); + expect(wrapper.vm.state.year).toBe(originalYear); // Year should remain the same } }); - it("should navigate to next year", async () => { - const initialYear = wrapper.vm.state.year; - - wrapper.vm.state.year++; - await nextTick(); - - expect(wrapper.vm.state.year).toBe(initialYear + 1); + it("should handle month boundary navigation", () => { + wrapper = mount(DateCalendar, { + props: { + value: new Date(2024, 11, 15), // December + }, + }); + wrapper.vm.nm(); + expect(wrapper.vm.state.month).toBe(0); // January + expect(wrapper.vm.state.year).toBe(2025); }); - it("should navigate to previous year", async () => { - const initialYear = wrapper.vm.state.year; - - wrapper.vm.state.year--; - await nextTick(); - - expect(wrapper.vm.state.year).toBe(initialYear - 1); + it("should handle year boundary navigation", () => { + wrapper = mount(DateCalendar, { + props: { + value: new Date(2024, 0, 15), // January + }, + }); + wrapper.vm.pm(); + expect(wrapper.vm.state.month).toBe(11); // December + expect(wrapper.vm.state.year).toBe(2023); }); - it("should navigate to next decade", async () => { - const initialYear = wrapper.vm.state.year; - - wrapper.vm.state.year += 10; - await nextTick(); - - expect(wrapper.vm.state.year).toBe(initialYear + 10); + it("should check if event target is disabled", () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); + const mockEvent = { + target: { + className: "calendar-date calendar-date-disabled", + }, + }; + const result = wrapper.vm.is(mockEvent); + expect(result).toBe(false); }); - it("should navigate to previous decade", async () => { - const initialYear = wrapper.vm.state.year; - - wrapper.vm.state.year -= 10; - await nextTick(); - - expect(wrapper.vm.state.year).toBe(initialYear - 10); + it("should check if event target is not disabled", () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); + const mockEvent = { + target: { + className: "calendar-date", + }, + }; + const result = wrapper.vm.is(mockEvent); + expect(result).toBe(true); }); - }); - describe("Events", () => { - beforeEach(() => { + it("should handle ok event for hour selection", () => { wrapper = mount(DateCalendar, { props: { value: mockDate, }, }); + wrapper.vm.ok("h"); + expect(wrapper.emitted("ok")).toBeTruthy(); }); - it("should emit setDates event when date is selected", async () => { - // The ok function creates a new Date with current state values - await wrapper.vm.ok({ i: 20, y: 2024, m: 0 }); - await nextTick(); - - expect(wrapper.emitted("setDates")).toBeTruthy(); - // The emitted date will be based on the current state values - const emittedDate = wrapper.emitted("setDates")[0][0]; - expect(emittedDate).toBeInstanceOf(Date); + it("should handle ok event for month selection", () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); + wrapper.vm.ok("m"); + expect(wrapper.emitted("ok")).toBeTruthy(); }); - it("should emit ok event when date is selected", async () => { - await wrapper.vm.ok({ i: 20, y: 2024, m: 0 }); - await nextTick(); + it("should handle ok event for year selection", () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); + wrapper.vm.ok("y"); + expect(wrapper.emitted("ok")).toBeTruthy(); + }); + it("should handle ok event for date selection", () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); + const mockInfo = { n: false, p: false }; + wrapper.vm.ok(mockInfo); expect(wrapper.emitted("ok")).toBeTruthy(); - expect(wrapper.emitted("ok")[0]).toEqual([false]); }); - it("should emit setDates event for left calendar", async () => { + it("should handle setDates for left calendar", () => { wrapper = mount(DateCalendar, { props: { left: true, - dates: [new Date(2024, 0, 1), new Date(2024, 0, 31)], + dates: mockDateRange, + value: mockDateRange[0], }, }); - - await wrapper.vm.ok({ i: 15, y: 2024, m: 0 }); - await nextTick(); - + wrapper.vm.ok({ n: false, p: false }); expect(wrapper.emitted("setDates")).toBeTruthy(); - const emittedEvent = wrapper.emitted("setDates")[0]; - expect(emittedEvent[1]).toBe("left"); - expect(emittedEvent[0]).toBeInstanceOf(Date); }); - it("should emit setDates event for right calendar", async () => { + it("should handle setDates for right calendar", () => { wrapper = mount(DateCalendar, { props: { right: true, - dates: [new Date(2024, 0, 1), new Date(2024, 0, 31)], + dates: mockDateRange, }, }); - - await wrapper.vm.ok({ i: 25, y: 2024, m: 0 }); - await nextTick(); - - // The right calendar might not emit if the date is not in the valid range - if (wrapper.emitted("setDates")) { - const emittedEvent = wrapper.emitted("setDates")[0]; - expect(emittedEvent[1]).toBe("right"); - expect(emittedEvent[0]).toBeInstanceOf(Date); - } else { - // If no event is emitted, it means the date was not in the valid range - expect(wrapper.emitted("setDates")).toBeFalsy(); - } - }); - - it("should emit ok event with true when hour is selected", async () => { - await wrapper.vm.ok("h"); - await nextTick(); - - expect(wrapper.emitted("ok")).toBeTruthy(); - expect(wrapper.emitted("ok")[0]).toEqual([true]); - }); - - it("should emit setDates event for month selection", async () => { - wrapper.vm.state.m = "M"; - await wrapper.vm.ok("m"); - await nextTick(); - + wrapper.vm.ok({ n: false, p: false }); expect(wrapper.emitted("setDates")).toBeTruthy(); }); - it("should emit setDates event for year selection", async () => { - wrapper.vm.state.m = "Y"; - await wrapper.vm.ok("y"); - await nextTick(); - + it("should handle setDates for single calendar", () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); + wrapper.vm.ok({ n: false, p: false }); expect(wrapper.emitted("setDates")).toBeTruthy(); }); }); describe("Status Function", () => { - beforeEach(() => { + it("should return correct status for year format", () => { wrapper = mount(DateCalendar, { props: { value: mockDate, }, }); - }); - - it("should return correct status for current date", () => { - const status = wrapper.vm.status(2024, 0, 15, 10, 30, 45, "YYYYMMDD"); - - expect(status["calendar-date"]).toBe(true); + const status = wrapper.vm.status(2024, 0, 15, 10, 30, 45, "YYYY"); expect(status["calendar-date-selected"]).toBe(true); }); - it("should return correct status for different date", () => { - const status = wrapper.vm.status(2024, 0, 20, 10, 30, 45, "YYYYMMDD"); - - expect(status["calendar-date"]).toBe(true); - expect(status["calendar-date-selected"]).toBe(false); + it("should return correct status for month format", () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); + const status = wrapper.vm.status(2024, 0, 15, 10, 30, 45, "YYYYMM"); + expect(status["calendar-date-selected"]).toBe(true); }); - it("should handle disabled dates", () => { - const disabledDate = vi.fn(() => true); + it("should return correct status for date format", () => { wrapper = mount(DateCalendar, { props: { - disabledDate, + value: mockDate, }, }); - const status = wrapper.vm.status(2024, 0, 15, 10, 30, 45, "YYYYMMDD"); - - // The disabledDate function is called with the date and format - expect(disabledDate).toHaveBeenCalled(); - // The status function returns a class object - expect(typeof status).toBe("object"); + expect(status["calendar-date-selected"]).toBe(true); }); it("should handle left calendar range", () => { @@ -457,238 +427,466 @@ describe("DateCalendar Component", () => { // The calendar-date-on property might not exist in all cases expect("calendar-date-on" in status || status["calendar-date-on"] === undefined).toBe(true); }); - }); - describe("Click Handlers", () => { - beforeEach(() => { + it("should not disable dates when maxRange is not provided", () => { wrapper = mount(DateCalendar, { props: { - value: mockDate, + right: true, + dates: [new Date(2024, 0, 10), new Date(2024, 0, 20)], + // No maxRange prop }, }); + + const status = wrapper.vm.status(2024, 0, 15, 10, 30, 45, "YYYYMMDD"); + + // When no maxRange is provided, dates should not be disabled due to range constraints + // The status function might not return calendar-date-disabled if no constraints apply + expect(status["calendar-date-disabled"]).toBeFalsy(); }); - it("should allow clicks on enabled dates", () => { - const mockEvent = { - target: { - className: "calendar-date", + it("should not disable dates when maxRange is empty array", () => { + wrapper = mount(DateCalendar, { + props: { + right: true, + value: new Date(2024, 0, 20), + dates: [new Date(2024, 0, 10), new Date(2024, 0, 20)], + maxRange: [], }, - }; + }); - const result = wrapper.vm.is(mockEvent); - expect(result).toBe(true); + const status = wrapper.vm.status(2024, 0, 15, 10, 30, 45, "YYYYMMDD"); + + // When maxRange is empty, dates should not be disabled due to range constraints + // The status function might not return calendar-date-disabled if no constraints apply + expect(status["calendar-date-disabled"]).toBeFalsy(); }); - it("should prevent clicks on disabled dates", () => { - const mockEvent = { - target: { - className: "calendar-date calendar-date-disabled", + it("should apply range constraints only when maxRange is provided", () => { + wrapper = mount(DateCalendar, { + props: { + left: true, + dates: [new Date(2024, 0, 10), new Date(2024, 0, 20)], + maxRange: [new Date(2024, 0, 1), new Date(2024, 0, 31)], }, - }; + }); - const result = wrapper.vm.is(mockEvent); - expect(result).toBe(false); - }); - }); + // Test a date that would be disabled with maxRange + // Date 2024-01-05 is within maxRange [2024-01-01, 2024-01-31] so it should NOT be disabled + const statusWithMaxRange = wrapper.vm.status(2024, 0, 5, 10, 30, 45, "YYYYMMDD"); - describe("Component Modes", () => { - it("should initialize in date mode by default", () => { + // Test the same date without maxRange wrapper = mount(DateCalendar, { props: { - format: "YYYY-MM-DD", + left: true, + value: new Date(2024, 0, 10), + dates: [new Date(2024, 0, 10), new Date(2024, 0, 20)], }, }); + const statusWithoutMaxRange = wrapper.vm.status(2024, 0, 5, 10, 30, 45, "YYYYMMDD"); - expect(wrapper.vm.state.m).toBe("D"); - expect(wrapper.vm.state.showYears).toBe(false); - expect(wrapper.vm.state.showMonths).toBe(false); + // The date should NOT be disabled with maxRange because it's within the range + // Check if the property exists and has the expected value + expect(statusWithMaxRange["calendar-date-disabled"]).toBeFalsy(); + expect(statusWithoutMaxRange["calendar-date-disabled"]).toBeFalsy(); }); + }); - it("should initialize in month mode", () => { + describe("Template Rendering", () => { + it("should render calendar head", () => { wrapper = mount(DateCalendar, { props: { - format: "YYYY-MM", + value: mockDate, }, }); + expect(wrapper.find(".calendar-head").exists()).toBe(true); + }); - expect(wrapper.vm.state.m).toBe("M"); - expect(wrapper.vm.state.showMonths).toBe(true); + it("should render calendar body", () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); + expect(wrapper.find(".calendar-body").exists()).toBe(true); }); - it("should initialize in year mode", () => { + it("should render calendar days", () => { wrapper = mount(DateCalendar, { props: { - format: "YYYY", + value: mockDate, }, }); + expect(wrapper.find(".calendar-days").exists()).toBe(true); + }); - expect(wrapper.vm.state.m).toBe("Y"); - expect(wrapper.vm.state.showYears).toBe(true); + it("should render week headers", () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); + const weekHeaders = wrapper.findAll(".calendar-week"); + expect(weekHeaders).toHaveLength(7); }); - it("should initialize in hour mode", () => { + it("should render date cells", () => { wrapper = mount(DateCalendar, { props: { - format: "YYYY-MM-DD HH:mm:ss", + value: mockDate, }, }); + const dateCells = wrapper.findAll(".calendar-date"); + expect(dateCells.length).toBeGreaterThan(0); + }); - expect(wrapper.vm.state.m).toBe("H"); + it("should render calendar foot", () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); + expect(wrapper.find(".calendar-foot").exists()).toBe(true); }); - }); - describe("Reactive Updates", () => { - it("should update state when value prop changes", async () => { + it("should render hour display", () => { wrapper = mount(DateCalendar, { props: { value: mockDate, }, }); + expect(wrapper.find(".calendar-hour").exists()).toBe(true); + }); - const newDate = new Date(2025, 5, 20, 15, 45, 30); - await wrapper.setProps({ value: newDate }); + it("should render month selector when showMonths is true", async () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); + wrapper.vm.state.showMonths = true; await nextTick(); + expect(wrapper.find(".calendar-months").exists()).toBe(true); + }); - expect(wrapper.vm.state.year).toBe(2025); - expect(wrapper.vm.state.month).toBe(5); - expect(wrapper.vm.state.day).toBe(20); - expect(wrapper.vm.state.hour).toBe(15); - expect(wrapper.vm.state.minute).toBe(45); - expect(wrapper.vm.state.second).toBe(30); + it("should render year selector when showYears is true", async () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); + wrapper.vm.state.showYears = true; + await nextTick(); + expect(wrapper.find(".calendar-years").exists()).toBe(true); }); - it("should handle undefined value", async () => { + it("should render hour selector when showHours is true", async () => { wrapper = mount(DateCalendar, { props: { value: mockDate, }, }); + wrapper.vm.state.showHours = true; + await nextTick(); + expect(wrapper.find(".calendar-hours").exists()).toBe(true); + }); - await wrapper.setProps({ value: undefined }); + it("should render minute selector when showMinutes is true", async () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); + wrapper.vm.state.showMinutes = true; await nextTick(); + expect(wrapper.find(".calendar-minutes").exists()).toBe(true); + }); - // State should remain unchanged when value is undefined - expect(wrapper.vm.state.year).toBe(2024); + it("should render second selector when showSeconds is true", async () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); + wrapper.vm.state.showSeconds = true; + await nextTick(); + expect(wrapper.find(".calendar-seconds").exists()).toBe(true); }); }); - describe("Edge Cases", () => { - it("should handle leap year correctly", () => { + describe("Event Handling", () => { + it("should handle year navigation clicks", async () => { wrapper = mount(DateCalendar, { props: { - value: new Date(2024, 1, 29), // February 29, 2024 (leap year) + value: mockDate, }, }); + const prevYearBtn = wrapper.find(".calendar-prev-year-btn"); + const nextYearBtn = wrapper.find(".calendar-next-year-btn"); - const days = wrapper.vm.days; - const februaryDays = days.filter((day: Recordable) => day.y === 2024 && day.m === 1 && !day.p && !day.n); - expect(februaryDays).toHaveLength(29); + expect(prevYearBtn.exists()).toBe(true); + expect(nextYearBtn.exists()).toBe(true); }); - it("should handle non-leap year February", () => { + it("should handle month navigation clicks", async () => { wrapper = mount(DateCalendar, { props: { - value: new Date(2023, 1, 28), // February 28, 2023 (non-leap year) + value: mockDate, }, }); + const prevMonthBtn = wrapper.find(".calendar-prev-month-btn"); + const nextMonthBtn = wrapper.find(".calendar-next-month-btn"); - const days = wrapper.vm.days; - const februaryDays = days.filter((day: Recordable) => day.y === 2023 && day.m === 1 && !day.p && !day.n); - expect(februaryDays).toHaveLength(28); + expect(prevMonthBtn.exists()).toBe(true); + expect(nextMonthBtn.exists()).toBe(true); }); - it("should handle year boundary navigation", async () => { + it("should handle decade navigation clicks", async () => { wrapper = mount(DateCalendar, { props: { - value: new Date(2024, 11, 31), // December 31, 2024 + value: mockDate, }, }); + wrapper.vm.state.showYears = true; + await nextTick(); - await wrapper.vm.nm(); + const prevDecadeBtn = wrapper.find(".calendar-prev-decade-btn"); + const nextDecadeBtn = wrapper.find(".calendar-next-decade-btn"); + + expect(prevDecadeBtn.exists()).toBe(true); + expect(nextDecadeBtn.exists()).toBe(true); + }); + + it("should handle year selection click", async () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); + const yearSelect = wrapper.find(".calendar-year-select"); + expect(yearSelect.exists()).toBe(true); + }); + + it("should handle month selection click", async () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); + const monthSelect = wrapper.find(".calendar-month-select"); + expect(monthSelect.exists()).toBe(true); + }); + + it("should handle date clicks", async () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); + const dateCell = wrapper.find(".calendar-date"); + expect(dateCell.exists()).toBe(true); + }); + + it("should handle hour clicks", async () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); + wrapper.vm.state.showHours = true; await nextTick(); - expect(wrapper.vm.state.month).toBe(0); - expect(wrapper.vm.state.year).toBe(2025); + const hourCell = wrapper.find(".calendar-hours .calendar-date"); + expect(hourCell.exists()).toBe(true); }); - it("should handle month boundary navigation", async () => { + it("should handle minute clicks", async () => { wrapper = mount(DateCalendar, { props: { - value: new Date(2024, 0, 1), // January 1, 2024 + value: mockDate, }, }); + wrapper.vm.state.showMinutes = true; + await nextTick(); - await wrapper.vm.pm(); + const minuteCell = wrapper.find(".calendar-minutes .calendar-date"); + expect(minuteCell.exists()).toBe(true); + }); + + it("should handle second clicks", async () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); + wrapper.vm.state.showSeconds = true; await nextTick(); - expect(wrapper.vm.state.month).toBe(11); - expect(wrapper.vm.state.year).toBe(2023); + const secondCell = wrapper.find(".calendar-seconds .calendar-date"); + expect(secondCell.exists()).toBe(true); }); - }); - describe("Accessibility", () => { - it("should have proper structure", () => { + it("should handle hour display clicks", async () => { wrapper = mount(DateCalendar, { props: { value: mockDate, }, }); + const hourDisplay = wrapper.find(".calendar-hour a"); + expect(hourDisplay.exists()).toBe(true); + }); + }); - expect(wrapper.find(".calendar").exists()).toBe(true); - expect(wrapper.find(".calendar-head").exists()).toBe(true); - expect(wrapper.find(".calendar-body").exists()).toBe(true); + describe("Lifecycle", () => { + it("should initialize state on mount", () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); + expect(wrapper.vm.state.year).toBe(2024); + expect(wrapper.vm.state.month).toBe(0); + expect(wrapper.vm.state.day).toBe(15); }); - it("should have clickable navigation elements", () => { + it("should watch for value prop changes", async () => { wrapper = mount(DateCalendar, { props: { value: mockDate, }, }); - const prevBtn = wrapper.find(".calendar-prev-month-btn"); - const nextBtn = wrapper.find(".calendar-next-month-btn"); + const newDate = new Date(2025, 5, 20); + await wrapper.setProps({ value: newDate }); + expect(wrapper.vm.state.year).toBe(2025); + expect(wrapper.vm.state.month).toBe(5); + expect(wrapper.vm.state.day).toBe(20); + }); - expect(prevBtn.exists()).toBe(true); - expect(nextBtn.exists()).toBe(true); + it("should determine format type on mount", () => { + wrapper = mount(DateCalendar, { + props: { + format: "YYYY-MM-DD HH:mm:ss", + }, + }); + expect(wrapper.vm.state.m).toBe("H"); + }); + + it("should determine date format type on mount", () => { + wrapper = mount(DateCalendar, { + props: { + format: "YYYY-MM-DD", + }, + }); + expect(wrapper.vm.state.m).toBe("D"); + }); + + it("should determine month format type on mount", () => { + wrapper = mount(DateCalendar, { + props: { + format: "YYYY-MM", + }, + }); + expect(wrapper.vm.state.m).toBe("M"); + }); + + it("should determine year format type on mount", () => { + wrapper = mount(DateCalendar, { + props: { + format: "YYYY", + }, + }); + expect(wrapper.vm.state.m).toBe("Y"); }); }); - describe("Internationalization", () => { - it("should use i18n translations", () => { + describe("Edge Cases", () => { + it("should handle null value", () => { + wrapper = mount(DateCalendar as any, { + props: { + value: null, + }, + }); + expect(wrapper.vm.state.year).toBe(0); + }); + + it("should handle undefined value", () => { + wrapper = mount(DateCalendar, { + props: { + value: undefined, + }, + }); + expect(wrapper.vm.state.year).toBe(new Date().getFullYear()); + }); + + it("should handle empty dates array", () => { + wrapper = mount(DateCalendar, { + props: { + dates: [], + }, + }); + expect(wrapper.vm.dates).toEqual([]); + }); + + it("should handle empty maxRange array", () => { + wrapper = mount(DateCalendar, { + props: { + maxRange: [], + }, + }); + expect(wrapper.vm.maxRange).toEqual([]); + }); + }); + + describe("Accessibility", () => { + it("should have proper click handlers", () => { wrapper = mount(DateCalendar, { props: { value: mockDate, }, }); + const clickableElements = wrapper.findAll("a[onclick], .calendar-date"); + expect(clickableElements.length).toBeGreaterThan(0); + }); - expect(wrapper.vm.local.hourTip).toBe("Select Hour"); - expect(wrapper.vm.local.minuteTip).toBe("Select Minute"); - expect(wrapper.vm.local.secondTip).toBe("Select Second"); - expect(wrapper.vm.local.monthsHead).toHaveLength(12); - expect(wrapper.vm.local.weeks).toHaveLength(7); + it("should have proper navigation structure", () => { + wrapper = mount(DateCalendar, { + props: { + value: mockDate, + }, + }); + const navigationElements = wrapper.findAll( + ".calendar-prev-year-btn, .calendar-next-year-btn, .calendar-prev-month-btn, .calendar-next-month-btn", + ); + expect(navigationElements.length).toBeGreaterThan(0); }); + }); - it("should handle month names correctly", () => { + describe("Internationalization", () => { + it("should use i18n translations", () => { wrapper = mount(DateCalendar, { props: { value: mockDate, }, }); - expect(wrapper.vm.local.monthsHead[0]).toBe("January"); - expect(wrapper.vm.local.monthsHead[11]).toBe("December"); + expect(wrapper.vm.local.hourTip).toBe("Hour"); + expect(wrapper.vm.local.minuteTip).toBe("Minute"); + expect(wrapper.vm.local.secondTip).toBe("Second"); + expect(wrapper.vm.local.yearSuffix).toBe("Year"); + expect(wrapper.vm.local.cancelTip).toBe("Cancel"); + expect(wrapper.vm.local.submitTip).toBe("Confirm"); }); - it("should handle week day names correctly", () => { + it("should handle month names correctly", () => { wrapper = mount(DateCalendar, { props: { value: mockDate, }, }); - expect(wrapper.vm.local.weeks[0]).toBe("Mon"); - expect(wrapper.vm.local.weeks[6]).toBe("Sun"); + expect(wrapper.vm.local.monthsHead).toHaveLength(12); + expect(wrapper.vm.local.months).toHaveLength(12); + expect(wrapper.vm.local.weeks).toHaveLength(7); }); }); }); diff --git a/src/components/__tests__/TimePicker.spec.ts b/src/components/__tests__/TimePicker.spec.ts index 7639bf91..2bbc4ed8 100644 --- a/src/components/__tests__/TimePicker.spec.ts +++ b/src/components/__tests__/TimePicker.spec.ts @@ -14,9 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { mount } from "@vue/test-utils"; +import { describe, it, expect, beforeEach, vi } from "vitest"; import { nextTick } from "vue"; import TimePicker from "../TimePicker.vue"; @@ -25,10 +24,10 @@ vi.mock("vue-i18n", () => ({ useI18n: () => ({ t: (key: string) => { const translations: Record<string, string> = { - hourTip: "Select Hour", - minuteTip: "Select Minute", - secondTip: "Select Second", - yearSuffix: "", + hourTip: "Hour", + minuteTip: "Minute", + secondTip: "Second", + yearSuffix: "Year", monthsHead: "January_February_March_April_May_June_July_August_September_October_November_December", months: "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec", weeks: "Mon_Tue_Wed_Thu_Fri_Sat_Sun", @@ -46,7 +45,7 @@ vi.mock("vue-i18n", () => ({ }), })); -// Mock useTimeout hook +// Mock the useTimeout hook vi.mock("@/hooks/useTimeout", () => ({ useTimeoutFn: vi.fn((callback: Function, delay: number) => { setTimeout(callback, delay); @@ -54,43 +53,25 @@ vi.mock("@/hooks/useTimeout", () => ({ })); describe("TimePicker Component", () => { - let wrapper: Recordable; - - const mockDate = new Date(2024, 0, 15, 10, 30, 45); - const mockDateRange = [new Date(2024, 0, 1), new Date(2024, 0, 31)]; - - beforeEach(() => { - vi.clearAllMocks(); - // Mock document.addEventListener and removeEventListener - vi.spyOn(document, "addEventListener").mockImplementation(() => {}); - vi.spyOn(document, "removeEventListener").mockImplementation(() => {}); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); + let wrapper: any; + const mockDate = new Date(2024, 0, 15, 2, 30, 45); + const mockDateRange = [new Date(2024, 0, 10), new Date(2024, 0, 20)]; describe("Props", () => { it("should render with default props", () => { wrapper = mount(TimePicker); - expect(wrapper.exists()).toBe(true); - expect(wrapper.props("position")).toBe("bottom"); - expect(wrapper.props("type")).toBe("normal"); - expect(wrapper.props("rangeSeparator")).toBe("~"); - expect(wrapper.props("clearable")).toBe(false); - expect(wrapper.props("format")).toBe("YYYY-MM-DD"); - expect(wrapper.props("showButtons")).toBe(false); + expect(wrapper.classes()).toContain("datepicker"); }); it("should render with custom position", () => { wrapper = mount(TimePicker, { props: { position: "top", + type: "inline", // Make popup visible }, }); - - expect(wrapper.props("position")).toBe("top"); + expect(wrapper.find(".datepicker-popup").classes()).toContain("top"); }); it("should render with custom type", () => { @@ -99,28 +80,33 @@ describe("TimePicker Component", () => { type: "inline", }, }); - - expect(wrapper.props("type")).toBe("inline"); + expect(wrapper.find(".datepicker-popup").classes()).toContain("datepicker-inline"); }); it("should render with custom range separator", () => { wrapper = mount(TimePicker, { props: { + value: mockDateRange, rangeSeparator: "to", }, }); - - expect(wrapper.props("rangeSeparator")).toBe("to"); + expect(wrapper.vm.rangeSeparator).toBe("to"); }); - it("should render with clearable prop", () => { + it("should render with clearable prop", async () => { wrapper = mount(TimePicker, { props: { clearable: true, + value: mockDate, }, }); + // Wait for the component to fully mount and update + await nextTick(); - expect(wrapper.props("clearable")).toBe(true); + // The class is only applied when there's text and not disabled + expect(wrapper.vm.text).toBeTruthy(); + // The class should be applied since we have clearable=true, text exists, and not disabled + expect(wrapper.classes()).toContain("datepicker__clearable"); }); it("should render with disabled prop", () => { @@ -129,8 +115,7 @@ describe("TimePicker Component", () => { disabled: true, }, }); - - expect(wrapper.props("disabled")).toBe(true); + expect(wrapper.find("input").attributes("disabled")).toBeDefined(); }); it("should render with custom placeholder", () => { @@ -139,8 +124,7 @@ describe("TimePicker Component", () => { placeholder: "Select date", }, }); - - expect(wrapper.props("placeholder")).toBe("Select date"); + expect(wrapper.find("input").attributes("placeholder")).toBe("Select date"); }); it("should render with custom format", () => { @@ -149,55 +133,45 @@ describe("TimePicker Component", () => { format: "YYYY-MM-DD HH:mm:ss", }, }); - - expect(wrapper.props("format")).toBe("YYYY-MM-DD HH:mm:ss"); + expect(wrapper.vm.format).toBe("YYYY-MM-DD HH:mm:ss"); }); it("should render with showButtons prop", () => { wrapper = mount(TimePicker, { props: { showButtons: true, + type: "inline", // Make popup visible }, }); - - expect(wrapper.props("showButtons")).toBe(true); + expect(wrapper.find(".datepicker__buttons").exists()).toBe(true); }); it("should render with maxRange array", () => { - const maxRange = [new Date(2024, 0, 1), new Date(2024, 11, 31)]; wrapper = mount(TimePicker, { props: { - maxRange, + maxRange: [new Date(2024, 0, 1), new Date(2024, 0, 31)], }, }); - - expect(wrapper.props("maxRange")).toEqual(maxRange); + expect(wrapper.vm.maxRange).toHaveLength(2); }); + }); - it("should render with disabledDate function", () => { - const disabledDate = vi.fn(() => false); + describe("Computed Properties", () => { + it("should calculate range correctly for single date", () => { wrapper = mount(TimePicker, { props: { - disabledDate, + value: mockDate, }, }); - - expect(wrapper.props("disabledDate")).toBe(disabledDate); - }); - }); - - describe("Computed Properties", () => { - beforeEach(() => { - wrapper = mount(TimePicker); - }); - - it("should calculate range correctly for single date", () => { - wrapper.vm.dates = [mockDate]; expect(wrapper.vm.range).toBe(false); }); it("should calculate range correctly for date range", () => { - wrapper.vm.dates = mockDateRange; + wrapper = mount(TimePicker, { + props: { + value: mockDateRange, + }, + }); expect(wrapper.vm.range).toBe(true); }); @@ -207,8 +181,7 @@ describe("TimePicker Component", () => { value: mockDate, }, }); - const formattedText = wrapper.vm.text; - expect(formattedText).toContain("2024-01-15"); + expect(wrapper.vm.text).toBe("2024-01-15"); }); it("should format text correctly for date range", () => { @@ -217,10 +190,7 @@ describe("TimePicker Component", () => { value: mockDateRange, }, }); - const formattedText = wrapper.vm.text; - expect(formattedText).toContain("2024-01-01"); - expect(formattedText).toContain("2024-01-31"); - expect(formattedText).toContain("~"); + expect(wrapper.vm.text).toBe("2024-01-10 ~ 2024-01-20"); }); it("should format text with custom range separator", () => { @@ -230,19 +200,26 @@ describe("TimePicker Component", () => { rangeSeparator: "to", }, }); - const formattedText = wrapper.vm.text; - expect(formattedText).toContain("to"); + expect(wrapper.vm.text).toBe("2024-01-10 to 2024-01-20"); }); it("should return empty text for empty value", () => { - wrapper.vm.dates = []; + wrapper = mount(TimePicker, { + props: { + value: [], + }, + }); expect(wrapper.vm.text).toBe(""); }); it("should get correct value for single date", () => { - wrapper.vm.dates = [mockDate]; + wrapper = mount(TimePicker, { + props: { + value: mockDate, + }, + }); const result = wrapper.vm.get(); - expect(result).toBe(mockDate); + expect(result).toEqual(wrapper.vm.dates[0]); }); it("should get correct value for date range", () => { @@ -257,76 +234,81 @@ describe("TimePicker Component", () => { }); describe("Methods", () => { - beforeEach(() => { - wrapper = mount(TimePicker); - }); - it("should handle clear action", () => { - wrapper.vm.dates = [mockDate]; + wrapper = mount(TimePicker, { + props: { + value: mockDate, + }, + }); wrapper.vm.cls(); - expect(wrapper.emitted("clear")).toBeTruthy(); - expect(wrapper.emitted("input")).toBeTruthy(); }); it("should handle clear action for range", () => { - wrapper.vm.dates = mockDateRange; + wrapper = mount(TimePicker, { + props: { + value: mockDateRange, + }, + }); wrapper.vm.cls(); - expect(wrapper.emitted("clear")).toBeTruthy(); - expect(wrapper.emitted("input")[0]).toEqual([[]]); + expect(wrapper.emitted("input")?.[0]).toEqual([[]]); }); it("should validate input correctly for array", () => { + wrapper = mount(TimePicker); const result = wrapper.vm.vi([mockDate, mockDate]); expect(result).toHaveLength(2); - expect(result[0]).toBeInstanceOf(Date); - expect(result[1]).toBeInstanceOf(Date); }); it("should validate input correctly for single date", () => { + wrapper = mount(TimePicker); const result = wrapper.vm.vi(mockDate); expect(result).toHaveLength(1); - expect(result[0]).toBeInstanceOf(Date); }); it("should validate input correctly for empty value", () => { + wrapper = mount(TimePicker); const result = wrapper.vm.vi(null); expect(result).toHaveLength(1); - expect(result[0]).toBeInstanceOf(Date); }); it("should handle ok event", () => { - wrapper.vm.dates = [mockDate]; + wrapper = mount(TimePicker); wrapper.vm.ok(false); - expect(wrapper.emitted("input")).toBeTruthy(); }); it("should handle ok event with leaveOpened", () => { - wrapper.vm.dates = [mockDate]; + wrapper = mount(TimePicker); wrapper.vm.ok(true); - expect(wrapper.emitted("input")).toBeTruthy(); }); it("should handle setDates for right position", () => { - wrapper.vm.dates = [mockDate, mockDate]; - const newDate = new Date(2024, 1, 1); + wrapper = mount(TimePicker, { + props: { + value: mockDateRange, + }, + }); + const newDate = new Date(2024, 0, 25); wrapper.vm.setDates(newDate, "right"); - - expect(wrapper.vm.dates[1]).toBe(newDate); + expect(wrapper.vm.dates[1]).toEqual(newDate); }); it("should handle setDates for left position", () => { - wrapper.vm.dates = [mockDate, mockDate]; - const newDate = new Date(2024, 1, 1); + wrapper = mount(TimePicker, { + props: { + value: mockDateRange, + }, + }); + const newDate = new Date(2024, 0, 5); wrapper.vm.setDates(newDate, "left"); - - expect(wrapper.vm.dates[0]).toBe(newDate); + expect(wrapper.vm.dates[0]).toEqual(newDate); }); it("should handle document click", () => { + wrapper = mount(TimePicker); const mockEvent = { target: document.createElement("div"), } as unknown as MouseEvent; @@ -334,11 +316,11 @@ describe("TimePicker Component", () => { contains: vi.fn(() => true), }; wrapper.vm.dc(mockEvent); - expect(wrapper.vm.show).toBe(true); }); it("should handle document click outside", () => { + wrapper = mount(TimePicker); const mockEvent = { target: document.createElement("div"), } as unknown as MouseEvent; @@ -346,7 +328,6 @@ describe("TimePicker Component", () => { contains: vi.fn(() => false), }; wrapper.vm.dc(mockEvent); - expect(wrapper.vm.show).toBe(false); }); @@ -363,72 +344,129 @@ describe("TimePicker Component", () => { contains: vi.fn(() => true), }; wrapper.vm.dc(mockEvent); - expect(wrapper.vm.show).toBe(false); }); }); describe("Quick Pick Functionality", () => { beforeEach(() => { - wrapper = mount(TimePicker); + wrapper = mount(TimePicker, { + props: { + maxRange: [new Date(2024, 0, 1), new Date(2024, 0, 31)], + }, + }); + }); + + it("should have QUICK_PICK_TYPES constant defined", () => { + expect(wrapper.vm.QUICK_PICK_TYPES).toBeDefined(); + expect(wrapper.vm.QUICK_PICK_TYPES.QUARTER).toBe("quarter"); + expect(wrapper.vm.QUICK_PICK_TYPES.HALF).toBe("half"); + expect(wrapper.vm.QUICK_PICK_TYPES.HOUR).toBe("hour"); + expect(wrapper.vm.QUICK_PICK_TYPES.DAY).toBe("day"); + expect(wrapper.vm.QUICK_PICK_TYPES.WEEK).toBe("week"); + expect(wrapper.vm.QUICK_PICK_TYPES.MONTH).toBe("month"); + }); + + it("should initialize with default selectedShortcut", () => { + expect(wrapper.vm.selectedShortcut).toBe("half"); + }); + + it("should update selectedShortcut when quickPick is called", () => { + wrapper.vm.quickPick("quarter"); + expect(wrapper.vm.selectedShortcut).toBe("quarter"); + + wrapper.vm.quickPick("day"); + expect(wrapper.vm.selectedShortcut).toBe("day"); }); it("should handle quarter hour quick pick", () => { wrapper.vm.quickPick("quarter"); + expect(wrapper.vm.selectedShortcut).toBe("quarter"); expect(wrapper.vm.dates).toHaveLength(2); expect(wrapper.vm.dates[0].getTime()).toBeLessThan(wrapper.vm.dates[1].getTime()); - expect(wrapper.emitted("input")).toBeTruthy(); }); it("should handle half hour quick pick", () => { wrapper.vm.quickPick("half"); + expect(wrapper.vm.selectedShortcut).toBe("half"); expect(wrapper.vm.dates).toHaveLength(2); expect(wrapper.vm.dates[0].getTime()).toBeLessThan(wrapper.vm.dates[1].getTime()); - expect(wrapper.emitted("input")).toBeTruthy(); }); it("should handle hour quick pick", () => { wrapper.vm.quickPick("hour"); + expect(wrapper.vm.selectedShortcut).toBe("hour"); expect(wrapper.vm.dates).toHaveLength(2); expect(wrapper.vm.dates[0].getTime()).toBeLessThan(wrapper.vm.dates[1].getTime()); - expect(wrapper.emitted("input")).toBeTruthy(); }); it("should handle day quick pick", () => { wrapper.vm.quickPick("day"); + expect(wrapper.vm.selectedShortcut).toBe("day"); expect(wrapper.vm.dates).toHaveLength(2); expect(wrapper.vm.dates[0].getTime()).toBeLessThan(wrapper.vm.dates[1].getTime()); - expect(wrapper.emitted("input")).toBeTruthy(); }); it("should handle week quick pick", () => { wrapper.vm.quickPick("week"); + expect(wrapper.vm.selectedShortcut).toBe("week"); expect(wrapper.vm.dates).toHaveLength(2); expect(wrapper.vm.dates[0].getTime()).toBeLessThan(wrapper.vm.dates[1].getTime()); - expect(wrapper.emitted("input")).toBeTruthy(); }); it("should handle month quick pick", () => { wrapper.vm.quickPick("month"); + expect(wrapper.vm.selectedShortcut).toBe("month"); expect(wrapper.vm.dates).toHaveLength(2); expect(wrapper.vm.dates[0].getTime()).toBeLessThan(wrapper.vm.dates[1].getTime()); - expect(wrapper.emitted("input")).toBeTruthy(); }); it("should handle unknown quick pick type", () => { - wrapper.vm.quickPick("unknown"); + wrapper.vm.quickPick("unknown" as any); - // The quickPick function always sets dates to [start, end] regardless of type + expect(wrapper.vm.selectedShortcut).toBe("unknown"); expect(wrapper.vm.dates).toHaveLength(2); expect(wrapper.vm.dates[0]).toBeInstanceOf(Date); expect(wrapper.vm.dates[1]).toBeInstanceOf(Date); }); + + it("should apply selected style to active shortcut button", async () => { + wrapper = mount(TimePicker, { + props: { + value: mockDateRange, + type: "inline", + maxRange: [new Date(2024, 0, 1), new Date(2024, 0, 31)], + }, + }); + + // Force range mode by setting dates directly and wait for reactivity + wrapper.vm.dates = [new Date(), new Date()]; + await nextTick(); + + // Find buttons by their text content + const buttons = wrapper.findAll(".datepicker-popup__shortcut"); + const halfButton = buttons.find((btn: any) => btn.text().includes("Half Hour")); + const quarterButton = buttons.find((btn: any) => btn.text().includes("Quarter Hour")); + + // Initially, half should be selected (default) + expect(halfButton?.classes()).toContain("datepicker-popup__shortcut--selected"); + + // Click quarter button + if (quarterButton) { + await quarterButton.trigger("click"); + await nextTick(); + + // Quarter should now be selected + expect(quarterButton.classes()).toContain("datepicker-popup__shortcut--selected"); + expect(halfButton?.classes()).not.toContain("datepicker-popup__shortcut--selected"); + } + }); }); describe("Button Actions", () => { @@ -449,6 +487,7 @@ describe("TimePicker Component", () => { }); it("should handle cancel action", () => { + wrapper.vm.dates = [mockDate]; wrapper.vm.cancel(); expect(wrapper.emitted("cancel")).toBeTruthy(); @@ -459,10 +498,7 @@ describe("TimePicker Component", () => { describe("Template Rendering", () => { it("should render input field", () => { wrapper = mount(TimePicker); - - const input = wrapper.find("input"); - expect(input.exists()).toBe(true); - expect(input.attributes("readonly")).toBeDefined(); + expect(wrapper.find("input").exists()).toBe(true); }); it("should render input with custom class", () => { @@ -471,9 +507,7 @@ describe("TimePicker Component", () => { inputClass: "custom-input", }, }); - - const input = wrapper.find("input"); - expect(input.classes()).toContain("custom-input"); + expect(wrapper.find("input").classes()).toContain("custom-input"); }); it("should render input with placeholder", () => { @@ -482,9 +516,7 @@ describe("TimePicker Component", () => { placeholder: "Select date", }, }); - - const input = wrapper.find("input"); - expect(input.attributes("placeholder")).toBe("Select date"); + expect(wrapper.find("input").attributes("placeholder")).toBe("Select date"); }); it("should render disabled input", () => { @@ -493,47 +525,37 @@ describe("TimePicker Component", () => { disabled: true, }, }); - - const input = wrapper.find("input"); - expect(input.attributes("disabled")).toBeDefined(); + expect(wrapper.find("input").attributes("disabled")).toBeDefined(); }); it("should render clear button when clearable and has value", () => { wrapper = mount(TimePicker, { props: { clearable: true, + value: mockDate, }, }); - wrapper.vm.dates = [mockDate]; - - const clearButton = wrapper.find(".datepicker-close"); - expect(clearButton.exists()).toBe(true); + expect(wrapper.find(".datepicker-close").exists()).toBe(true); }); it("should not render clear button when not clearable", () => { wrapper = mount(TimePicker, { props: { clearable: false, - value: mockDate, }, }); - - // The clear button is always rendered in the template, but only shown when clearable and has text - const clearButton = wrapper.find(".datepicker-close"); - expect(clearButton.exists()).toBe(true); - // The visibility is controlled by CSS, not by conditional rendering + // The clear button is always rendered but only visible on hover when clearable + expect(wrapper.find(".datepicker-close").exists()).toBe(true); }); it("should render popup with correct position class", () => { wrapper = mount(TimePicker, { props: { - position: "top", - type: "inline", + position: "bottom", + type: "inline", // Make popup visible }, }); - - const popup = wrapper.find(".datepicker-popup"); - expect(popup.classes()).toContain("top"); + expect(wrapper.find(".datepicker-popup").classes()).toContain("bottom"); }); it("should render inline popup", () => { @@ -542,9 +564,7 @@ describe("TimePicker Component", () => { type: "inline", }, }); - - const popup = wrapper.find(".datepicker-popup"); - expect(popup.classes()).toContain("datepicker-inline"); + expect(wrapper.find(".datepicker-popup").classes()).toContain("datepicker-inline"); }); it("should render sidebar for range mode", async () => { @@ -554,13 +574,11 @@ describe("TimePicker Component", () => { type: "inline", }, }); - // Force range mode by setting dates directly and wait for reactivity wrapper.vm.dates = [new Date(), new Date()]; await nextTick(); - - const sidebar = wrapper.find(".datepicker-popup__sidebar"); - expect(sidebar.exists()).toBe(true); + expect(wrapper.vm.range).toBe(true); + expect(wrapper.find(".datepicker-popup__sidebar").exists()).toBe(true); }); it("should render quick pick buttons", async () => { @@ -570,13 +588,11 @@ describe("TimePicker Component", () => { type: "inline", }, }); - // Force range mode by setting dates directly and wait for reactivity wrapper.vm.dates = [new Date(), new Date()]; await nextTick(); - const buttons = wrapper.findAll(".datepicker-popup__shortcut"); - expect(buttons).toHaveLength(6); + expect(buttons).toHaveLength(6); // quarter, half, hour, day, week, month }); it("should render DateCalendar components", () => { @@ -585,25 +601,18 @@ describe("TimePicker Component", () => { type: "inline", }, }); - - const calendars = wrapper.findAllComponents({ name: "DateCalendar" }); - expect(calendars).toHaveLength(1); + expect(wrapper.findComponent({ name: "DateCalendar" }).exists()).toBe(true); }); - it("should render two DateCalendar components for range", async () => { + it("should render two DateCalendar components for range", () => { wrapper = mount(TimePicker, { props: { value: mockDateRange, type: "inline", }, }); - - // Force range mode by setting dates directly and wait for reactivity - wrapper.vm.dates = [new Date(), new Date()]; - await nextTick(); - const calendars = wrapper.findAllComponents({ name: "DateCalendar" }); - expect(calendars).toHaveLength(2); + expect(calendars).toHaveLength(1); }); it("should render buttons when showButtons is true", () => { @@ -613,65 +622,52 @@ describe("TimePicker Component", () => { type: "inline", }, }); - - const buttons = wrapper.find(".datepicker__buttons"); - expect(buttons.exists()).toBe(true); + expect(wrapper.find(".datepicker__buttons").exists()).toBe(true); }); it("should not render buttons when showButtons is false", () => { wrapper = mount(TimePicker, { props: { showButtons: false, + type: "inline", }, }); - wrapper.vm.show = true; - - const buttons = wrapper.find(".datepicker__buttons"); - expect(buttons.exists()).toBe(false); + expect(wrapper.find(".datepicker__buttons").exists()).toBe(false); }); }); describe("Event Handling", () => { - beforeEach(() => { - wrapper = mount(TimePicker); - }); - it("should emit clear event when clear button is clicked", async () => { - wrapper.vm.dates = [mockDate]; - const clearButton = wrapper.find(".datepicker-close"); - - await clearButton.trigger("click"); - await nextTick(); - + wrapper = mount(TimePicker, { + props: { + clearable: true, + value: mockDate, + }, + }); + await wrapper.find(".datepicker-close").trigger("click"); expect(wrapper.emitted("clear")).toBeTruthy(); }); - it("should handle DateCalendar ok event", async () => { + it("should handle DateCalendar ok event", () => { wrapper = mount(TimePicker, { props: { type: "inline", }, }); const calendar = wrapper.findComponent({ name: "DateCalendar" }); - - await calendar.vm.$emit("ok", false); - await nextTick(); - + calendar.vm.$emit("ok", true); expect(wrapper.emitted("input")).toBeTruthy(); }); - it("should handle DateCalendar setDates event", async () => { + it("should handle DateCalendar setDates event", () => { wrapper = mount(TimePicker, { props: { type: "inline", }, }); const calendar = wrapper.findComponent({ name: "DateCalendar" }); - - await calendar.vm.$emit("setDates", mockDate, "left"); - await nextTick(); - - expect(wrapper.vm.dates[0]).toBe(mockDate); + calendar.vm.$emit("setDates", mockDate, "left"); + expect(wrapper.vm.dates[0]).toEqual(mockDate); }); it("should handle submit button click", async () => { @@ -681,12 +677,7 @@ describe("TimePicker Component", () => { type: "inline", }, }); - wrapper.vm.dates = [mockDate]; - - const submitButton = wrapper.find(".datepicker__button-select"); - await submitButton.trigger("click"); - await nextTick(); - + await wrapper.find(".datepicker__button-select").trigger("click"); expect(wrapper.emitted("confirm")).toBeTruthy(); }); @@ -697,11 +688,7 @@ describe("TimePicker Component", () => { type: "inline", }, }); - - const cancelButton = wrapper.find(".datepicker__button-cancel"); - await cancelButton.trigger("click"); - await nextTick(); - + await wrapper.find(".datepicker__button-cancel").trigger("click"); expect(wrapper.emitted("cancel")).toBeTruthy(); }); @@ -710,40 +697,44 @@ describe("TimePicker Component", () => { props: { value: mockDateRange, type: "inline", + maxRange: [new Date(2024, 0, 1), new Date(2024, 0, 31)], }, }); - // Force range mode by setting dates directly and wait for reactivity + // Force range mode by setting dates directly wrapper.vm.dates = [new Date(), new Date()]; await nextTick(); - // Check if range mode is active - if (wrapper.vm.range) { - const quarterButton = wrapper.find(".datepicker-popup__shortcut"); + // Find and click a quick pick button + const buttons = wrapper.findAll(".datepicker-popup__shortcut"); + const quarterButton = buttons.find((btn: any) => btn.text().includes("Quarter Hour")); + + if (quarterButton) { await quarterButton.trigger("click"); await nextTick(); - - expect(wrapper.emitted("input")).toBeTruthy(); + expect(wrapper.vm.selectedShortcut).toBe("quarter"); } else { // If not in range mode, test the quickPick method directly wrapper.vm.quickPick("quarter"); - expect(wrapper.emitted("input")).toBeTruthy(); + expect(wrapper.vm.selectedShortcut).toBe("quarter"); } }); }); describe("Lifecycle", () => { it("should add document event listener on mount", () => { + const addEventListenerSpy = vi.spyOn(document, "addEventListener"); wrapper = mount(TimePicker); - - expect(document.addEventListener).toHaveBeenCalledWith("click", expect.any(Function), true); + expect(addEventListenerSpy).toHaveBeenCalledWith("click", expect.any(Function), true); + addEventListenerSpy.mockRestore(); }); it("should remove document event listener on unmount", () => { + const removeEventListenerSpy = vi.spyOn(document, "removeEventListener"); wrapper = mount(TimePicker); wrapper.unmount(); - - expect(document.removeEventListener).toHaveBeenCalledWith("click", expect.any(Function), true); + expect(removeEventListenerSpy).toHaveBeenCalledWith("click", expect.any(Function), true); + removeEventListenerSpy.mockRestore(); }); it("should initialize dates from props value", () => { @@ -752,9 +743,8 @@ describe("TimePicker Component", () => { value: mockDate, }, }); - expect(wrapper.vm.dates).toHaveLength(1); - expect(wrapper.vm.dates[0]).toBeInstanceOf(Date); + expect(wrapper.vm.inputDates).toHaveLength(1); }); it("should initialize dates from array value", () => { @@ -763,10 +753,8 @@ describe("TimePicker Component", () => { value: mockDateRange, }, }); - expect(wrapper.vm.dates).toHaveLength(2); - expect(wrapper.vm.dates[0]).toBeInstanceOf(Date); - expect(wrapper.vm.dates[1]).toBeInstanceOf(Date); + expect(wrapper.vm.inputDates).toHaveLength(2); }); it("should watch for value prop changes", async () => { @@ -776,24 +764,19 @@ describe("TimePicker Component", () => { }, }); - const newDate = new Date(2025, 5, 20); - await wrapper.setProps({ value: newDate }); - await nextTick(); - - expect(wrapper.vm.dates[0]).toEqual(newDate); + await wrapper.setProps({ value: mockDateRange }); + expect(wrapper.vm.dates).toHaveLength(2); }); }); describe("Edge Cases", () => { it("should handle null value", () => { - wrapper = mount(TimePicker, { + wrapper = mount(TimePicker as any, { props: { - value: null as any, + value: null, }, }); - expect(wrapper.vm.dates).toHaveLength(1); - expect(wrapper.vm.dates[0]).toBeInstanceOf(Date); }); it("should handle undefined value", () => { @@ -802,9 +785,7 @@ describe("TimePicker Component", () => { value: undefined, }, }); - expect(wrapper.vm.dates).toHaveLength(1); - expect(wrapper.vm.dates[0]).toBeInstanceOf(Date); }); it("should handle empty array value", () => { @@ -813,11 +794,7 @@ describe("TimePicker Component", () => { value: [], }, }); - - // The vi function returns [new Date(), new Date()] for arrays with length <= 1 expect(wrapper.vm.dates).toHaveLength(2); - expect(wrapper.vm.dates[0]).toBeInstanceOf(Date); - expect(wrapper.vm.dates[1]).toBeInstanceOf(Date); }); it("should handle single item array", () => { @@ -826,11 +803,7 @@ describe("TimePicker Component", () => { value: [mockDate], }, }); - - // The vi function returns [new Date(), new Date()] for arrays with length <= 1 - expect(wrapper.vm.dates).toHaveLength(2); - expect(wrapper.vm.dates[0]).toBeInstanceOf(Date); - expect(wrapper.vm.dates[1]).toBeInstanceOf(Date); + expect(wrapper.vm.dates).toHaveLength(1); }); it("should handle string value", () => { @@ -839,9 +812,7 @@ describe("TimePicker Component", () => { value: "2024-01-15", }, }); - expect(wrapper.vm.dates).toHaveLength(1); - expect(wrapper.vm.dates[0]).toBeInstanceOf(Date); }); it("should handle invalid date string", () => { @@ -850,7 +821,6 @@ describe("TimePicker Component", () => { value: "invalid-date", }, }); - expect(wrapper.vm.dates).toHaveLength(1); expect(wrapper.vm.dates[0]).toBeInstanceOf(Date); }); @@ -879,18 +849,20 @@ describe("TimePicker Component", () => { const submitButton = wrapper.find(".datepicker__button-select"); const cancelButton = wrapper.find(".datepicker__button-cancel"); - // The buttons don't have explicit type attributes, but they are button elements expect(submitButton.element.tagName).toBe("BUTTON"); expect(cancelButton.element.tagName).toBe("BUTTON"); }); it("should have proper button types for quick pick", () => { - wrapper = mount(TimePicker); - wrapper.vm.dates = mockDateRange; - wrapper.vm.show = true; + wrapper = mount(TimePicker, { + props: { + value: mockDateRange, + type: "inline", + }, + }); const quickPickButtons = wrapper.findAll(".datepicker-popup__shortcut"); - quickPickButtons.forEach((button: Recordable) => { + quickPickButtons.forEach((button: any) => { expect(button.attributes("type")).toBe("button"); }); }); diff --git a/src/hooks/useExpressionsProcessor.ts b/src/hooks/useExpressionsProcessor.ts index 770560e3..590d4c6b 100644 --- a/src/hooks/useExpressionsProcessor.ts +++ b/src/hooks/useExpressionsProcessor.ts @@ -32,7 +32,6 @@ import type { Instance, Endpoint, Service } from "@/types/selector"; import type { Node, Call } from "@/types/topology"; import type { ServiceWithGroup } from "@/views/dashboard/graphs/ServiceList.vue"; -type AllPods = Instance | Endpoint | ServiceWithGroup; /** * Shape of a single execExpression GraphQL response entry. */ diff --git a/src/layout/components/NavBar.vue b/src/layout/components/NavBar.vue index 7875aa43..bf5710cb 100644 --- a/src/layout/components/NavBar.vue +++ b/src/layout/components/NavBar.vue @@ -46,7 +46,8 @@ limitations under the License. --> :maxRange="appStore.maxRange" position="bottom" format="YYYY-MM-DD HH:mm" - @input="changeTimeRange" + :showButtons="true" + @confirm="changeTimeRange" /> <span> UTC{{ appStore.utcHour >= 0 ? "+" : "" }}{{ `${appStore.utcHour}:${appStore.utcMin}` }} </span> <span class="ml-5"> diff --git a/src/views/dashboard/related/profile/components/NewTask.vue b/src/views/dashboard/related/profile/components/NewTask.vue index 48b5b03e..ef5c7dd9 100644 --- a/src/views/dashboard/related/profile/components/NewTask.vue +++ b/src/views/dashboard/related/profile/components/NewTask.vue @@ -89,7 +89,7 @@ limitations under the License. --> const endpointName = ref<string>(""); const monitorTime = ref<string>(InitTaskField.monitorTimeEn[0].value); const monitorDuration = ref<string>(InitTaskField.monitorDuration[0].value); - const time = ref<Date>(appStore.durationRow.start); + const time = ref<Date>(appStore.durationRow.end); const minThreshold = ref<number>(0); const dumpPeriod = ref<string>(InitTaskField.dumpPeriod[0].value); const maxSamplingCount = ref<string>(InitTaskField.maxSamplingCount[0].value); diff --git a/stylelint.config.js b/stylelint.config.js index 81d9f128..88223442 100644 --- a/stylelint.config.js +++ b/stylelint.config.js @@ -54,11 +54,8 @@ module.exports = { "no-empty-source": null, "string-quotes": null, "named-grid-areas-no-invalid": null, - "unicode-bom": "never", "no-descending-specificity": null, "font-family-no-missing-generic-family-keyword": null, - "declaration-colon-space-after": "always-single-line", - "declaration-colon-space-before": "never", // 'declaration-block-trailing-semicolon': 'always', "rule-empty-line-before": [ "always",