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 fc631381 test: implement comprehensive unit tests for components (#487)
fc631381 is described below

commit fc631381c74689f3051b0917036024c3ab10e3ce
Author: Fine0830 <fanxue0...@gmail.com>
AuthorDate: Wed Aug 6 18:35:45 2025 +0800

    test: implement comprehensive unit tests for components (#487)
---
 src/__tests__/App.spec.ts                          |   8 -
 .../Graph/{Selector.vue => GraphSelector.vue}      |   0
 src/components/Graph/Legend.vue                    |   4 +-
 src/components/Radio.vue                           |   9 +-
 src/components/SelectSingle.vue                    |  17 +-
 src/components/__tests__/DateCalendar.spec.ts      | 694 ++++++++++++++++
 src/components/__tests__/Radio.spec.ts             | 520 ++++++++++++
 src/components/__tests__/SelectSingle.spec.ts      | 488 +++++++++++
 src/components/__tests__/Selector.spec.ts          | 402 +++++++++
 src/components/__tests__/TimePicker.spec.ts        | 921 +++++++++++++++++++++
 src/store/modules/demand-log.ts                    |   3 +
 src/store/modules/event.ts                         |   6 +-
 src/store/modules/log.ts                           |   3 +
 src/store/modules/selectors.ts                     |   4 +-
 src/store/modules/trace.ts                         |   6 +
 src/types/components.d.ts                          |   1 +
 .../continuous-profiling/components/PolicyList.vue |   2 +-
 .../dashboard/related/trace/utils/d3-trace-list.ts |   1 +
 .../dashboard/related/trace/utils/d3-trace-tree.ts |   1 +
 19 files changed, 3068 insertions(+), 22 deletions(-)

diff --git a/src/__tests__/App.spec.ts b/src/__tests__/App.spec.ts
index 15cb545e..f1a083c2 100644
--- a/src/__tests__/App.spec.ts
+++ b/src/__tests__/App.spec.ts
@@ -93,8 +93,6 @@ describe("App Component", () => {
   });
 
   it("should apply correct CSS classes", () => {
-    const wrapper = mount(App);
-
     // The App component itself doesn't have the 'app' class, it's on the #app 
element
     const appElement = document.getElementById("app");
     expect(appElement?.className).toContain("app");
@@ -163,9 +161,6 @@ describe("App Component", () => {
 
   it("should not throw errors for undefined route names", async () => {
     mockRoute.name = undefined;
-
-    const wrapper = mount(App);
-
     // Should not throw error
     expect(() => {
       vi.advanceTimersByTime(500);
@@ -174,9 +169,6 @@ describe("App Component", () => {
 
   it("should handle null route names", async () => {
     mockRoute.name = null;
-
-    const wrapper = mount(App);
-
     // Should not throw error
     expect(() => {
       vi.advanceTimersByTime(500);
diff --git a/src/components/Graph/Selector.vue 
b/src/components/Graph/GraphSelector.vue
similarity index 100%
rename from src/components/Graph/Selector.vue
rename to src/components/Graph/GraphSelector.vue
diff --git a/src/components/Graph/Legend.vue b/src/components/Graph/Legend.vue
index 4cb566c9..38172839 100644
--- a/src/components/Graph/Legend.vue
+++ b/src/components/Graph/Legend.vue
@@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 
or implied.
 See the License for the specific language governing permissions and
 limitations under the License. -->
 <template>
-  <Selector
+  <GraphSelector
     class="mb-10"
     multiple
     :value="legend"
@@ -30,7 +30,7 @@ limitations under the License. -->
   import { computed, ref, watch } from "vue";
   import type { PropType } from "vue";
   import type { Option } from "@/types/app";
-  import Selector from "./Selector.vue";
+  import GraphSelector from "./GraphSelector.vue";
 
   const props = defineProps({
     data: {
diff --git a/src/components/Radio.vue b/src/components/Radio.vue
index 3f96381b..17e21593 100644
--- a/src/components/Radio.vue
+++ b/src/components/Radio.vue
@@ -20,7 +20,7 @@ limitations under the License. -->
   </el-radio-group>
 </template>
 <script lang="ts" setup>
-  import { ref } from "vue";
+  import { ref, watch } from "vue";
   import type { PropType } from "vue";
 
   /*global  defineProps, defineEmits */
@@ -47,4 +47,11 @@ limitations under the License. -->
   function checked(opt: unknown) {
     emit("change", opt);
   }
+
+  watch(
+    () => props.value,
+    (newValue) => {
+      selected.value = newValue;
+    },
+  );
 </script>
diff --git a/src/components/SelectSingle.vue b/src/components/SelectSingle.vue
index 796df39b..d4b80451 100644
--- a/src/components/SelectSingle.vue
+++ b/src/components/SelectSingle.vue
@@ -64,22 +64,25 @@ limitations under the License. -->
     selected.value = { label: "", value: "" };
     emit("change", "");
   }
-  watch(
-    () => props.value,
-    (data) => {
-      const opt = props.options.find((d: Option) => data === d.value);
-      selected.value = opt || { label: "", value: "" };
-    },
-  );
+
   document.body.addEventListener("click", handleClick, false);
 
   function handleClick() {
     visible.value = false;
   }
+
   function setPopper(event: MouseEvent) {
     event.stopPropagation();
     visible.value = !visible.value;
   }
+
+  watch(
+    () => props.value,
+    (data) => {
+      const opt = props.options.find((d: Option) => data === d.value);
+      selected.value = opt || { label: "", value: "" };
+    },
+  );
 </script>
 <style lang="scss" scoped>
   .bar-select {
diff --git a/src/components/__tests__/DateCalendar.spec.ts 
b/src/components/__tests__/DateCalendar.spec.ts
new file mode 100644
index 00000000..1e9b754e
--- /dev/null
+++ b/src/components/__tests__/DateCalendar.spec.ts
@@ -0,0 +1,694 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { mount } from "@vue/test-utils";
+import { nextTick } from "vue";
+import DateCalendar from "../DateCalendar.vue";
+
+// Mock vue-i18n
+vi.mock("vue-i18n", () => ({
+  useI18n: () => ({
+    t: (key: string) => {
+      const translations: Record<string, string> = {
+        hourTip: "Select Hour",
+        minuteTip: "Select Minute",
+        secondTip: "Select Second",
+        yearSuffix: "",
+        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",
+        cancel: "Cancel",
+        confirm: "Confirm",
+        quarterHourCutTip: "Quarter Hour",
+        halfHourCutTip: "Half Hour",
+        hourCutTip: "Hour",
+        dayCutTip: "Day",
+        weekCutTip: "Week",
+        monthCutTip: "Month",
+      };
+      return translations[key] || key;
+    },
+  }),
+}));
+
+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();
+  });
+
+  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);
+    });
+
+    it("should render with custom value", () => {
+      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);
+    });
+
+    it("should render with left prop", () => {
+      wrapper = mount(DateCalendar, {
+        props: {
+          left: true,
+        },
+      });
+
+      expect(wrapper.props("left")).toBe(true);
+    });
+
+    it("should render with right prop", () => {
+      wrapper = mount(DateCalendar, {
+        props: {
+          right: true,
+        },
+      });
+
+      expect(wrapper.props("right")).toBe(true);
+    });
+
+    it("should render with custom format", () => {
+      wrapper = mount(DateCalendar, {
+        props: {
+          format: "YYYY-MM-DD HH:mm:ss",
+        },
+      });
+
+      expect(wrapper.props("format")).toBe("YYYY-MM-DD HH:mm:ss");
+    });
+
+    it("should render with dates array", () => {
+      const dates = [new Date(2024, 0, 1), new Date(2024, 0, 31)];
+      wrapper = mount(DateCalendar, {
+        props: {
+          dates,
+        },
+      });
+
+      expect(wrapper.props("dates")).toEqual(dates);
+    });
+
+    it("should render with maxRange array", () => {
+      const maxRange = [new Date(2024, 0, 1), new Date(2024, 11, 31)];
+      wrapper = mount(DateCalendar, {
+        props: {
+          maxRange,
+        },
+      });
+
+      expect(wrapper.props("maxRange")).toEqual(maxRange);
+    });
+
+    it("should render with disabledDate function", () => {
+      const disabledDate = vi.fn(() => false);
+      wrapper = mount(DateCalendar, {
+        props: {
+          disabledDate,
+        },
+      });
+
+      expect(wrapper.props("disabledDate")).toBe(disabledDate);
+    });
+  });
+
+  describe("Computed Properties", () => {
+    beforeEach(() => {
+      wrapper = mount(DateCalendar, {
+        props: {
+          value: mockDate,
+        },
+      });
+    });
+
+    it("should calculate start date correctly", () => {
+      const dates = [new Date(2024, 0, 1), new Date(2024, 0, 31)];
+      wrapper = mount(DateCalendar, {
+        props: {
+          dates,
+        },
+      });
+
+      // The actual value depends on the parse function implementation
+      expect(wrapper.vm.start).toBeGreaterThan(0);
+    });
+
+    it("should calculate end date correctly", () => {
+      const dates = [new Date(2024, 0, 1), new Date(2024, 0, 31)];
+      wrapper = mount(DateCalendar, {
+        props: {
+          dates,
+        },
+      });
+
+      // The actual value depends on the parse function implementation
+      expect(wrapper.vm.end).toBeGreaterThan(0);
+    });
+
+    it("should calculate year start correctly", () => {
+      expect(wrapper.vm.ys).toBe(2020);
+    });
+
+    it("should calculate year end correctly", () => {
+      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 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");
+    });
+  });
+
+  describe("Navigation", () => {
+    beforeEach(() => {
+      wrapper = mount(DateCalendar, {
+        props: {
+          value: mockDate,
+        },
+      });
+    });
+
+    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);
+      }
+    });
+
+    it("should navigate to previous month", async () => {
+      const initialMonth = wrapper.vm.state.month;
+      const initialYear = wrapper.vm.state.year;
+
+      await wrapper.vm.pm();
+      await nextTick();
+
+      if (initialMonth === 0) {
+        expect(wrapper.vm.state.month).toBe(11);
+        expect(wrapper.vm.state.year).toBe(initialYear - 1);
+      } else {
+        expect(wrapper.vm.state.month).toBe(initialMonth - 1);
+        expect(wrapper.vm.state.year).toBe(initialYear);
+      }
+    });
+
+    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 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 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 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);
+    });
+  });
+
+  describe("Events", () => {
+    beforeEach(() => {
+      wrapper = mount(DateCalendar, {
+        props: {
+          value: mockDate,
+        },
+      });
+    });
+
+    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 emit ok event when date is selected", async () => {
+      await wrapper.vm.ok({ i: 20, y: 2024, m: 0 });
+      await nextTick();
+
+      expect(wrapper.emitted("ok")).toBeTruthy();
+      expect(wrapper.emitted("ok")[0]).toEqual([false]);
+    });
+
+    it("should emit setDates event for left calendar", async () => {
+      wrapper = mount(DateCalendar, {
+        props: {
+          left: true,
+          dates: [new Date(2024, 0, 1), new Date(2024, 0, 31)],
+        },
+      });
+
+      await wrapper.vm.ok({ i: 15, y: 2024, m: 0 });
+      await nextTick();
+
+      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 () => {
+      wrapper = mount(DateCalendar, {
+        props: {
+          right: true,
+          dates: [new Date(2024, 0, 1), new Date(2024, 0, 31)],
+        },
+      });
+
+      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();
+
+      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();
+
+      expect(wrapper.emitted("setDates")).toBeTruthy();
+    });
+  });
+
+  describe("Status Function", () => {
+    beforeEach(() => {
+      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);
+      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 handle disabled dates", () => {
+      const disabledDate = vi.fn(() => true);
+      wrapper = mount(DateCalendar, {
+        props: {
+          disabledDate,
+        },
+      });
+
+      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");
+    });
+
+    it("should handle left calendar range", () => {
+      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 status = wrapper.vm.status(2024, 0, 15, 10, 30, 45, "YYYYMMDD");
+
+      // Test that the status function returns the expected structure
+      expect(typeof status).toBe("object");
+      // The calendar-date-on property might not exist in all cases
+      expect("calendar-date-on" in status || status["calendar-date-on"] === 
undefined).toBe(true);
+    });
+
+    it("should handle right calendar range", () => {
+      wrapper = mount(DateCalendar, {
+        props: {
+          right: true,
+          dates: [new Date(2024, 0, 10), new Date(2024, 0, 20)],
+          maxRange: [new Date(2024, 0, 1), new Date(2024, 0, 31)],
+        },
+      });
+
+      const status = wrapper.vm.status(2024, 0, 15, 10, 30, 45, "YYYYMMDD");
+
+      // Test that the status function returns the expected structure
+      expect(typeof status).toBe("object");
+      // 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(() => {
+      wrapper = mount(DateCalendar, {
+        props: {
+          value: mockDate,
+        },
+      });
+    });
+
+    it("should allow clicks on enabled dates", () => {
+      const mockEvent = {
+        target: {
+          className: "calendar-date",
+        },
+      };
+
+      const result = wrapper.vm.is(mockEvent);
+      expect(result).toBe(true);
+    });
+
+    it("should prevent clicks on disabled dates", () => {
+      const mockEvent = {
+        target: {
+          className: "calendar-date calendar-date-disabled",
+        },
+      };
+
+      const result = wrapper.vm.is(mockEvent);
+      expect(result).toBe(false);
+    });
+  });
+
+  describe("Component Modes", () => {
+    it("should initialize in date mode by default", () => {
+      wrapper = mount(DateCalendar, {
+        props: {
+          format: "YYYY-MM-DD",
+        },
+      });
+
+      expect(wrapper.vm.state.m).toBe("D");
+      expect(wrapper.vm.state.showYears).toBe(false);
+      expect(wrapper.vm.state.showMonths).toBe(false);
+    });
+
+    it("should initialize in month mode", () => {
+      wrapper = mount(DateCalendar, {
+        props: {
+          format: "YYYY-MM",
+        },
+      });
+
+      expect(wrapper.vm.state.m).toBe("M");
+      expect(wrapper.vm.state.showMonths).toBe(true);
+    });
+
+    it("should initialize in year mode", () => {
+      wrapper = mount(DateCalendar, {
+        props: {
+          format: "YYYY",
+        },
+      });
+
+      expect(wrapper.vm.state.m).toBe("Y");
+      expect(wrapper.vm.state.showYears).toBe(true);
+    });
+
+    it("should initialize in hour mode", () => {
+      wrapper = mount(DateCalendar, {
+        props: {
+          format: "YYYY-MM-DD HH:mm:ss",
+        },
+      });
+
+      expect(wrapper.vm.state.m).toBe("H");
+    });
+  });
+
+  describe("Reactive Updates", () => {
+    it("should update state when value prop changes", async () => {
+      wrapper = mount(DateCalendar, {
+        props: {
+          value: mockDate,
+        },
+      });
+
+      const newDate = new Date(2025, 5, 20, 15, 45, 30);
+      await wrapper.setProps({ value: newDate });
+      await nextTick();
+
+      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 handle undefined value", async () => {
+      wrapper = mount(DateCalendar, {
+        props: {
+          value: mockDate,
+        },
+      });
+
+      await wrapper.setProps({ value: undefined });
+      await nextTick();
+
+      // State should remain unchanged when value is undefined
+      expect(wrapper.vm.state.year).toBe(2024);
+    });
+  });
+
+  describe("Edge Cases", () => {
+    it("should handle leap year correctly", () => {
+      wrapper = mount(DateCalendar, {
+        props: {
+          value: new Date(2024, 1, 29), // February 29, 2024 (leap year)
+        },
+      });
+
+      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);
+    });
+
+    it("should handle non-leap year February", () => {
+      wrapper = mount(DateCalendar, {
+        props: {
+          value: new Date(2023, 1, 28), // February 28, 2023 (non-leap year)
+        },
+      });
+
+      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);
+    });
+
+    it("should handle year boundary navigation", async () => {
+      wrapper = mount(DateCalendar, {
+        props: {
+          value: new Date(2024, 11, 31), // December 31, 2024
+        },
+      });
+
+      await wrapper.vm.nm();
+      await nextTick();
+
+      expect(wrapper.vm.state.month).toBe(0);
+      expect(wrapper.vm.state.year).toBe(2025);
+    });
+
+    it("should handle month boundary navigation", async () => {
+      wrapper = mount(DateCalendar, {
+        props: {
+          value: new Date(2024, 0, 1), // January 1, 2024
+        },
+      });
+
+      await wrapper.vm.pm();
+      await nextTick();
+
+      expect(wrapper.vm.state.month).toBe(11);
+      expect(wrapper.vm.state.year).toBe(2023);
+    });
+  });
+
+  describe("Accessibility", () => {
+    it("should have proper structure", () => {
+      wrapper = mount(DateCalendar, {
+        props: {
+          value: mockDate,
+        },
+      });
+
+      expect(wrapper.find(".calendar").exists()).toBe(true);
+      expect(wrapper.find(".calendar-head").exists()).toBe(true);
+      expect(wrapper.find(".calendar-body").exists()).toBe(true);
+    });
+
+    it("should have clickable navigation elements", () => {
+      wrapper = mount(DateCalendar, {
+        props: {
+          value: mockDate,
+        },
+      });
+
+      const prevBtn = wrapper.find(".calendar-prev-month-btn");
+      const nextBtn = wrapper.find(".calendar-next-month-btn");
+
+      expect(prevBtn.exists()).toBe(true);
+      expect(nextBtn.exists()).toBe(true);
+    });
+  });
+
+  describe("Internationalization", () => {
+    it("should use i18n translations", () => {
+      wrapper = mount(DateCalendar, {
+        props: {
+          value: mockDate,
+        },
+      });
+
+      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 handle month names correctly", () => {
+      wrapper = mount(DateCalendar, {
+        props: {
+          value: mockDate,
+        },
+      });
+
+      expect(wrapper.vm.local.monthsHead[0]).toBe("January");
+      expect(wrapper.vm.local.monthsHead[11]).toBe("December");
+    });
+
+    it("should handle week day names correctly", () => {
+      wrapper = mount(DateCalendar, {
+        props: {
+          value: mockDate,
+        },
+      });
+
+      expect(wrapper.vm.local.weeks[0]).toBe("Mon");
+      expect(wrapper.vm.local.weeks[6]).toBe("Sun");
+    });
+  });
+});
diff --git a/src/components/__tests__/Radio.spec.ts 
b/src/components/__tests__/Radio.spec.ts
new file mode 100644
index 00000000..713fe2bd
--- /dev/null
+++ b/src/components/__tests__/Radio.spec.ts
@@ -0,0 +1,520 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { mount, type VueWrapper } from "@vue/test-utils";
+import { nextTick } from "vue";
+import Radio from "../Radio.vue";
+
+describe("Radio Component", () => {
+  let wrapper: Recordable;
+
+  const mockOptions = [
+    { label: "Option 1", value: "option1" },
+    { label: "Option 2", value: "option2" },
+    { label: "Option 3", value: "option3" },
+  ];
+
+  const mockOptionsWithNumbers = [
+    { label: "Option 1", value: 1 },
+    { label: "Option 2", value: 2 },
+    { label: "Option 3", value: 3 },
+  ];
+
+  beforeEach(() => {
+    vi.clearAllMocks();
+  });
+
+  describe("Props", () => {
+    it("should render with default props", () => {
+      wrapper = mount(Radio);
+
+      expect(wrapper.exists()).toBe(true);
+      expect(wrapper.vm.selected).toBe("");
+      expect(wrapper.vm.options).toEqual([]);
+      expect(wrapper.vm.size).toBe("default");
+    });
+
+    it("should render with custom options", () => {
+      wrapper = mount(Radio, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      expect(wrapper.vm.options).toEqual(mockOptions);
+      expect(wrapper.findAll(".el-radio")).toHaveLength(3);
+    });
+
+    it("should render with custom value", () => {
+      wrapper = mount(Radio, {
+        props: {
+          options: mockOptions,
+          value: "option2",
+        },
+      });
+
+      expect(wrapper.vm.selected).toBe("option2");
+    });
+
+    it("should render with custom size", () => {
+      wrapper = mount(Radio, {
+        props: {
+          size: "small",
+        },
+      });
+
+      expect(wrapper.vm.size).toBe("small");
+    });
+
+    it("should handle options with number values", () => {
+      wrapper = mount(Radio, {
+        props: {
+          options: mockOptionsWithNumbers,
+          value: "2",
+        },
+      });
+
+      expect(wrapper.vm.options).toEqual(mockOptionsWithNumbers);
+      expect(wrapper.findAll(".el-radio")).toHaveLength(3);
+    });
+
+    it("should handle empty options array", () => {
+      wrapper = mount(Radio, {
+        props: {
+          options: [],
+        },
+      });
+
+      expect(wrapper.vm.options).toEqual([]);
+      expect(wrapper.findAll(".el-radio")).toHaveLength(0);
+    });
+
+    it("should handle undefined options", () => {
+      wrapper = mount(Radio, {
+        props: {
+          options: undefined,
+        },
+      });
+
+      expect(wrapper.vm.options).toEqual([]);
+    });
+  });
+
+  describe("Rendering", () => {
+    it("should render el-radio-group", () => {
+      wrapper = mount(Radio);
+
+      expect(wrapper.find(".el-radio-group").exists()).toBe(true);
+    });
+
+    it("should render correct number of radio options", () => {
+      wrapper = mount(Radio, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      const radioElements = wrapper.findAll(".el-radio");
+      expect(radioElements).toHaveLength(3);
+    });
+
+    it("should render radio labels correctly", () => {
+      wrapper = mount(Radio, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      const radioElements = wrapper.findAll(".el-radio");
+      expect(radioElements[0].text()).toContain("Option 1");
+      expect(radioElements[1].text()).toContain("Option 2");
+      expect(radioElements[2].text()).toContain("Option 3");
+    });
+
+    it("should set correct key attributes", () => {
+      wrapper = mount(Radio, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      // Vue doesn't expose key attributes directly, but we can verify the 
structure
+      const radioElements = wrapper.findAll(".el-radio");
+      expect(radioElements).toHaveLength(3);
+      // Verify that each radio has a unique structure based on the options
+      expect(radioElements[0].text()).toContain("Option 1");
+      expect(radioElements[1].text()).toContain("Option 2");
+      expect(radioElements[2].text()).toContain("Option 3");
+    });
+
+    it("should set correct label attributes", () => {
+      wrapper = mount(Radio, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      // Vue doesn't expose label attributes directly, but we can verify the 
structure
+      const radioElements = wrapper.findAll(".el-radio");
+      expect(radioElements).toHaveLength(3);
+      // Verify that each radio has the correct text content
+      expect(radioElements[0].text()).toContain("Option 1");
+      expect(radioElements[1].text()).toContain("Option 2");
+      expect(radioElements[2].text()).toContain("Option 3");
+    });
+
+    it("should handle mixed string and number labels", () => {
+      const mixedOptions = [
+        { label: "String Label", value: "string" },
+        { label: 123, value: "number" },
+      ];
+
+      wrapper = mount(Radio, {
+        props: {
+          options: mixedOptions,
+        },
+      });
+
+      const radioElements = wrapper.findAll(".el-radio");
+      expect(radioElements[0].text()).toContain("String Label");
+      expect(radioElements[1].text()).toContain("123");
+    });
+  });
+
+  describe("Events", () => {
+    it("should emit change event when radio is selected", async () => {
+      wrapper = mount(Radio, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      // Trigger the change event by calling the checked function directly
+      await wrapper.vm.checked("option2");
+      await nextTick();
+
+      expect(wrapper.emitted("change")).toBeTruthy();
+      expect(wrapper.emitted("change")[0]).toEqual(["option2"]);
+    });
+
+    it("should emit change event with correct value", async () => {
+      wrapper = mount(Radio, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      await wrapper.vm.checked("option3");
+      await nextTick();
+
+      expect(wrapper.emitted("change")[0]).toEqual(["option3"]);
+    });
+
+    it("should emit change event multiple times", async () => {
+      wrapper = mount(Radio, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      await wrapper.vm.checked("option1");
+      await nextTick();
+      await wrapper.vm.checked("option2");
+      await nextTick();
+
+      expect(wrapper.emitted("change")).toHaveLength(2);
+      expect(wrapper.emitted("change")[0]).toEqual(["option1"]);
+      expect(wrapper.emitted("change")[1]).toEqual(["option2"]);
+    });
+
+    it("should handle change event with number value", async () => {
+      wrapper = mount(Radio, {
+        props: {
+          options: mockOptionsWithNumbers,
+        },
+      });
+
+      await wrapper.vm.checked(2);
+      await nextTick();
+
+      expect(wrapper.emitted("change")[0]).toEqual([2]);
+    });
+  });
+
+  describe("Data Binding", () => {
+    it("should update selected value when props change", async () => {
+      wrapper = mount(Radio, {
+        props: {
+          options: mockOptions,
+          value: "option1",
+        },
+      });
+
+      expect(wrapper.vm.selected).toBe("option1");
+
+      await wrapper.setProps({ value: "option2" });
+      await nextTick();
+
+      expect(wrapper.vm.selected).toBe("option2");
+    });
+
+    it("should update options when props change", async () => {
+      wrapper = mount(Radio, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      expect(wrapper.vm.options).toEqual(mockOptions);
+
+      const newOptions = [
+        { label: "New Option 1", value: "new1" },
+        { label: "New Option 2", value: "new2" },
+      ];
+
+      await wrapper.setProps({ options: newOptions });
+      await nextTick();
+
+      expect(wrapper.vm.options).toEqual(newOptions);
+      expect(wrapper.findAll(".el-radio")).toHaveLength(2);
+    });
+
+    it("should maintain selected value when options change", async () => {
+      wrapper = mount(Radio, {
+        props: {
+          options: mockOptions,
+          value: "option2",
+        },
+      });
+
+      expect(wrapper.vm.selected).toBe("option2");
+
+      const newOptions = [
+        { label: "New Option 1", value: "new1" },
+        { label: "New Option 2", value: "new2" },
+      ];
+
+      await wrapper.setProps({ options: newOptions });
+      await nextTick();
+
+      expect(wrapper.vm.selected).toBe("option2"); // Should maintain the 
selected value
+    });
+  });
+
+  describe("Edge Cases", () => {
+    it("should handle null options", () => {
+      wrapper = mount(Radio, {
+        props: {
+          options: null as any,
+        },
+      });
+
+      // When null is passed, Vue will pass it through as-is
+      // The component should handle this gracefully in the template
+      expect(wrapper.vm.options).toBeNull();
+      // But the component should still render without errors
+      expect(wrapper.find(".el-radio-group").exists()).toBe(true);
+    });
+
+    it("should handle undefined value", () => {
+      wrapper = mount(Radio, {
+        props: {
+          value: undefined,
+        },
+      });
+
+      expect(wrapper.vm.selected).toBe("");
+    });
+
+    it("should handle empty string value", () => {
+      wrapper = mount(Radio, {
+        props: {
+          value: "",
+        },
+      });
+
+      expect(wrapper.vm.selected).toBe("");
+    });
+
+    it("should handle options with empty labels", () => {
+      const optionsWithEmptyLabels = [
+        { label: "", value: "empty" },
+        { label: "Valid Label", value: "valid" },
+      ];
+
+      wrapper = mount(Radio, {
+        props: {
+          options: optionsWithEmptyLabels,
+        },
+      });
+
+      expect(wrapper.vm.options).toEqual(optionsWithEmptyLabels);
+      expect(wrapper.findAll(".el-radio")).toHaveLength(2);
+    });
+
+    it("should handle options with empty values", () => {
+      const optionsWithEmptyValues = [
+        { label: "Label 1", value: "" },
+        { label: "Label 2", value: "valid" },
+      ];
+
+      wrapper = mount(Radio, {
+        props: {
+          options: optionsWithEmptyValues,
+        },
+      });
+
+      expect(wrapper.vm.options).toEqual(optionsWithEmptyValues);
+      expect(wrapper.findAll(".el-radio")).toHaveLength(2);
+    });
+
+    it("should handle very long labels", () => {
+      const longLabel = "A".repeat(1000);
+      const optionsWithLongLabel = [{ label: longLabel, value: "long" }];
+
+      wrapper = mount(Radio, {
+        props: {
+          options: optionsWithLongLabel,
+        },
+      });
+
+      expect(wrapper.vm.options).toEqual(optionsWithLongLabel);
+      expect(wrapper.findAll(".el-radio")).toHaveLength(1);
+    });
+
+    it("should handle special characters in labels", () => {
+      const specialOptions = [
+        { label: "Option with & symbols", value: "special1" },
+        { label: "Option with <script> tags", value: "special2" },
+        { label: "Option with 'quotes'", value: "special3" },
+      ];
+
+      wrapper = mount(Radio, {
+        props: {
+          options: specialOptions,
+        },
+      });
+
+      expect(wrapper.vm.options).toEqual(specialOptions);
+      expect(wrapper.findAll(".el-radio")).toHaveLength(3);
+    });
+  });
+
+  describe("Component Integration", () => {
+    it("should work with Element Plus radio components", () => {
+      wrapper = mount(Radio, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      const radioGroup = wrapper.find(".el-radio-group");
+      const radioElements = wrapper.findAll(".el-radio");
+
+      expect(radioGroup.exists()).toBe(true);
+      expect(radioElements.length).toBeGreaterThan(0);
+      // Verify the component structure is correct
+      expect(wrapper.vm.selected).toBe("");
+    });
+
+    it("should have correct v-model binding", () => {
+      wrapper = mount(Radio, {
+        props: {
+          options: mockOptions,
+          value: "option1",
+        },
+      });
+
+      // Verify the internal selected value matches the prop
+      expect(wrapper.vm.selected).toBe("option1");
+    });
+
+    it("should have correct change event binding", () => {
+      wrapper = mount(Radio, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      // Verify the component has the checked function
+      expect(typeof wrapper.vm.checked).toBe("function");
+    });
+  });
+
+  describe("Accessibility", () => {
+    it("should have proper ARIA attributes", () => {
+      wrapper = mount(Radio, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      const radioGroup = wrapper.find(".el-radio-group");
+      expect(radioGroup.exists()).toBe(true);
+    });
+
+    it("should render radio options with proper structure", () => {
+      wrapper = mount(Radio, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      const radioElements = wrapper.findAll(".el-radio");
+      radioElements.forEach((radio: VueWrapper) => {
+        expect(radio.exists()).toBe(true);
+        expect(radio.text()).toBeTruthy();
+      });
+    });
+  });
+
+  describe("Performance", () => {
+    it("should handle large number of options", () => {
+      const largeOptions = Array.from({ length: 100 }, (_, i) => ({
+        label: `Option ${i + 1}`,
+        value: `option${i + 1}`,
+      }));
+
+      wrapper = mount(Radio, {
+        props: {
+          options: largeOptions,
+        },
+      });
+
+      expect(wrapper.vm.options).toEqual(largeOptions);
+      expect(wrapper.findAll(".el-radio")).toHaveLength(100);
+    });
+
+    it("should handle rapid prop changes", async () => {
+      wrapper = mount(Radio, {
+        props: {
+          options: mockOptions,
+          value: "option1",
+        },
+      });
+
+      // Rapidly change props
+      await wrapper.setProps({ value: "option2" });
+      await wrapper.setProps({ value: "option3" });
+      await wrapper.setProps({ value: "option1" });
+      await nextTick();
+
+      expect(wrapper.vm.selected).toBe("option1");
+    });
+  });
+});
diff --git a/src/components/__tests__/SelectSingle.spec.ts 
b/src/components/__tests__/SelectSingle.spec.ts
new file mode 100644
index 00000000..0ed3f38f
--- /dev/null
+++ b/src/components/__tests__/SelectSingle.spec.ts
@@ -0,0 +1,488 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * 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 { nextTick } from "vue";
+import SelectSingle from "../SelectSingle.vue";
+
+describe("SelectSingle Component", () => {
+  let wrapper: Recordable;
+
+  const mockOptions = [
+    { label: "Option 1", value: "option1" },
+    { label: "Option 2", value: "option2" },
+    { label: "Option 3", value: "option3" },
+  ];
+
+  beforeEach(() => {
+    vi.clearAllMocks();
+    // Mock document.body.addEventListener
+    vi.spyOn(document.body, "addEventListener").mockImplementation(() => {});
+  });
+
+  afterEach(() => {
+    vi.restoreAllMocks();
+  });
+
+  describe("Props", () => {
+    it("should render with default props", () => {
+      wrapper = mount(SelectSingle);
+
+      expect(wrapper.exists()).toBe(true);
+      expect(wrapper.find(".bar-select").exists()).toBe(true);
+      expect(wrapper.find(".no-data").text()).toBe("Please select a option");
+    });
+
+    it("should render with custom options", () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      expect(wrapper.props("options")).toEqual(mockOptions);
+    });
+
+    it("should render with selected value", () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+          value: "option1",
+        },
+      });
+
+      expect(wrapper.vm.selected.value).toBe("option1");
+      expect(wrapper.vm.selected.label).toBe("Option 1");
+    });
+
+    it("should render with clearable option", () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+          value: "option1",
+          clearable: true,
+        },
+      });
+
+      expect(wrapper.props("clearable")).toBe(true);
+      expect(wrapper.find(".remove-icon").exists()).toBe(true);
+    });
+
+    it("should not show remove icon when clearable is false", () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+          value: "option1",
+          clearable: false,
+        },
+      });
+
+      expect(wrapper.find(".remove-icon").exists()).toBe(false);
+    });
+  });
+
+  describe("Component Structure", () => {
+    it("should have correct template structure", () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      expect(wrapper.find(".bar-select").exists()).toBe(true);
+      expect(wrapper.find(".bar-i").exists()).toBe(true);
+      expect(wrapper.find(".opt-wrapper").exists()).toBe(true);
+    });
+
+    it("should render options correctly", () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      const options = wrapper.findAll(".opt");
+      expect(options.length).toBe(3);
+      expect(options[0].text()).toBe("Option 1");
+      expect(options[1].text()).toBe("Option 2");
+      expect(options[2].text()).toBe("Option 3");
+    });
+
+    it("should show selected option text", () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+          value: "option1",
+        },
+      });
+
+      expect(wrapper.find(".bar-i span").text()).toBe("Option 1");
+    });
+
+    it("should show placeholder when no option is selected", () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+          value: "",
+        },
+      });
+
+      expect(wrapper.find(".no-data").text()).toBe("Please select a option");
+    });
+  });
+
+  describe("Event Handling", () => {
+    it("should emit change event when option is selected", async () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      // Open dropdown
+      await wrapper.find(".bar-i").trigger("click");
+      await nextTick();
+
+      // Select an option
+      await wrapper.find(".opt").trigger("click");
+
+      expect(wrapper.emitted("change")).toBeTruthy();
+      expect(wrapper.emitted("change")[0][0]).toBe("option1");
+    });
+
+    it("should emit change event with empty string when remove is clicked", 
async () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+          value: "option1",
+          clearable: true,
+        },
+      });
+
+      await wrapper.find(".remove-icon").trigger("click");
+
+      expect(wrapper.emitted("change")).toBeTruthy();
+      expect(wrapper.emitted("change")[0][0]).toBe("");
+    });
+
+    it("should toggle dropdown visibility when bar-i is clicked", async () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      expect(wrapper.vm.visible).toBe(false);
+
+      await wrapper.find(".bar-i").trigger("click");
+      expect(wrapper.vm.visible).toBe(true);
+
+      await wrapper.find(".bar-i").trigger("click");
+      expect(wrapper.vm.visible).toBe(false);
+    });
+
+    it("should not select disabled option", async () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+          value: "option1",
+        },
+      });
+
+      // Open dropdown
+      await wrapper.find(".bar-i").trigger("click");
+      await nextTick();
+
+      // Try to select the already selected option (which should be disabled)
+      const disabledOption = wrapper.find(".select-disabled");
+      expect(disabledOption.exists()).toBe(true);
+      expect(disabledOption.text()).toBe("Option 1");
+    });
+  });
+
+  describe("Watchers", () => {
+    it("should update selected value when props.value changes", async () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+          value: "option1",
+        },
+      });
+
+      expect(wrapper.vm.selected.value).toBe("option1");
+
+      await wrapper.setProps({
+        value: "option2",
+      });
+      await nextTick();
+
+      expect(wrapper.vm.selected.value).toBe("option2");
+      expect(wrapper.vm.selected.label).toBe("Option 2");
+    });
+
+    it("should handle value change to empty string", async () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+          value: "option1",
+        },
+      });
+
+      expect(wrapper.vm.selected.value).toBe("option1");
+
+      await wrapper.setProps({
+        value: "",
+      });
+      await nextTick();
+
+      expect(wrapper.vm.selected.value).toBe("");
+      expect(wrapper.vm.selected.label).toBe("");
+    });
+
+    it("should handle value change to non-existent option", async () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+          value: "option1",
+        },
+      });
+
+      expect(wrapper.vm.selected.value).toBe("option1");
+
+      await wrapper.setProps({
+        value: "nonexistent",
+      });
+      await nextTick();
+
+      expect(wrapper.vm.selected.value).toBe("");
+      expect(wrapper.vm.selected.label).toBe("");
+    });
+  });
+
+  describe("Methods", () => {
+    it("should handle select option correctly", async () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      await wrapper.vm.handleSelect(mockOptions[1]);
+
+      expect(wrapper.vm.selected.value).toBe("option2");
+      expect(wrapper.vm.selected.label).toBe("Option 2");
+      expect(wrapper.emitted("change")[0][0]).toBe("option2");
+    });
+
+    it("should handle remove selected correctly", async () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+          value: "option1",
+        },
+      });
+
+      await wrapper.vm.removeSelected();
+
+      expect(wrapper.vm.selected.value).toBe("");
+      expect(wrapper.vm.selected.label).toBe("");
+      expect(wrapper.emitted("change")[0][0]).toBe("");
+    });
+
+    it("should handle setPopper correctly", async () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      const event = { stopPropagation: vi.fn() };
+      await wrapper.vm.setPopper(event);
+
+      expect(event.stopPropagation).toHaveBeenCalled();
+      expect(wrapper.vm.visible).toBe(true);
+    });
+
+    it("should handle click outside correctly", async () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      // Open dropdown
+      await wrapper.find(".bar-i").trigger("click");
+      expect(wrapper.vm.visible).toBe(true);
+
+      // Simulate click outside
+      await wrapper.vm.handleClick();
+      expect(wrapper.vm.visible).toBe(false);
+    });
+  });
+
+  describe("Edge Cases", () => {
+    it("should handle empty options array", () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: [],
+        },
+      });
+
+      expect(wrapper.props("options")).toEqual([]);
+      expect(wrapper.findAll(".opt").length).toBe(0);
+    });
+
+    it("should handle undefined value", () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+          value: undefined,
+        },
+      });
+
+      expect(wrapper.vm.selected.value).toBe("");
+      expect(wrapper.vm.selected.label).toBe("");
+    });
+
+    it("should handle null value", () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+          value: null as any,
+        },
+      });
+
+      expect(wrapper.vm.selected.value).toBe("");
+      expect(wrapper.vm.selected.label).toBe("");
+    });
+
+    it("should handle options with empty values", () => {
+      const optionsWithEmptyValues = [
+        { label: "Option 1", value: "" },
+        { label: "Option 2", value: "option2" },
+      ];
+
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: optionsWithEmptyValues,
+          value: "",
+        },
+      });
+
+      expect(wrapper.vm.selected.value).toBe("");
+      expect(wrapper.vm.selected.label).toBe("Option 1");
+    });
+  });
+
+  describe("Integration", () => {
+    it("should work with all props combined", () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+          value: "option1",
+          clearable: true,
+        },
+      });
+
+      expect(wrapper.exists()).toBe(true);
+      expect(wrapper.vm.selected.value).toBe("option1");
+      expect(wrapper.vm.selected.label).toBe("Option 1");
+      expect(wrapper.props("clearable")).toBe(true);
+      expect(wrapper.find(".remove-icon").exists()).toBe(true);
+    });
+
+    it("should handle complete selection workflow", async () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+          clearable: true,
+        },
+      });
+
+      // Initially no selection
+      expect(wrapper.vm.selected.value).toBe("");
+      expect(wrapper.find(".no-data").text()).toBe("Please select a option");
+
+      // Open dropdown
+      await wrapper.find(".bar-i").trigger("click");
+      expect(wrapper.vm.visible).toBe(true);
+
+      // Select an option
+      await wrapper.find(".opt").trigger("click");
+      expect(wrapper.vm.selected.value).toBe("option1");
+      expect(wrapper.emitted("change")[0][0]).toBe("option1");
+
+      // Clear selection
+      await wrapper.find(".remove-icon").trigger("click");
+      expect(wrapper.vm.selected.value).toBe("");
+      expect(wrapper.emitted("change")[1][0]).toBe("");
+    });
+
+    it("should handle dropdown toggle and option selection", async () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      // Toggle dropdown
+      await wrapper.find(".bar-i").trigger("click");
+      expect(wrapper.vm.visible).toBe(true);
+
+      // Select option
+      const options = wrapper.findAll(".opt");
+      await options[1].trigger("click");
+
+      expect(wrapper.vm.selected.value).toBe("option2");
+      expect(wrapper.emitted("change")[0][0]).toBe("option2");
+      expect(wrapper.vm.visible).toBe(true); // Should stay open after 
selection
+    });
+  });
+
+  describe("CSS Classes", () => {
+    it("should apply active class when dropdown is visible", async () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      expect(wrapper.find(".bar-select").classes()).not.toContain("active");
+
+      await wrapper.find(".bar-i").trigger("click");
+      expect(wrapper.find(".bar-select").classes()).toContain("active");
+    });
+
+    it("should apply select-disabled class to selected option", async () => {
+      wrapper = mount(SelectSingle, {
+        props: {
+          options: mockOptions,
+          value: "option1",
+        },
+      });
+
+      await wrapper.find(".bar-i").trigger("click");
+      await nextTick();
+
+      const options = wrapper.findAll(".opt");
+      expect(options[0].classes()).toContain("select-disabled");
+      expect(options[1].classes()).not.toContain("select-disabled");
+      expect(options[2].classes()).not.toContain("select-disabled");
+    });
+  });
+});
diff --git a/src/components/__tests__/Selector.spec.ts 
b/src/components/__tests__/Selector.spec.ts
new file mode 100644
index 00000000..4b08fe26
--- /dev/null
+++ b/src/components/__tests__/Selector.spec.ts
@@ -0,0 +1,402 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { mount } from "@vue/test-utils";
+import { nextTick } from "vue";
+import Selector from "../Selector.vue";
+
+describe("Selector Component", () => {
+  let wrapper: Recordable;
+
+  const mockOptions = [
+    { label: "Option 1", value: "option1" },
+    { label: "Option 2", value: "option2" },
+    { label: "Option 3", value: "option3", disabled: true },
+  ];
+
+  beforeEach(() => {
+    vi.clearAllMocks();
+  });
+
+  describe("Props", () => {
+    it("should render with default props", () => {
+      wrapper = mount(Selector);
+
+      expect(wrapper.exists()).toBe(true);
+      expect(wrapper.vm.selected).toEqual([]);
+    });
+
+    it("should render with custom value", () => {
+      wrapper = mount(Selector, {
+        props: {
+          options: mockOptions,
+          value: "option1",
+        },
+      });
+
+      expect(wrapper.vm.selected).toBe("option1");
+    });
+
+    it("should render in multiple mode", () => {
+      wrapper = mount(Selector, {
+        props: {
+          options: mockOptions,
+          multiple: true,
+          value: ["option1", "option2"],
+        },
+      });
+
+      expect(wrapper.vm.selected).toEqual(["option1", "option2"]);
+    });
+
+    it("should render with custom placeholder", () => {
+      wrapper = mount(Selector, {
+        props: {
+          placeholder: "Custom placeholder",
+        },
+      });
+
+      expect(wrapper.props("placeholder")).toBe("Custom placeholder");
+    });
+
+    it("should render with custom size", () => {
+      wrapper = mount(Selector, {
+        props: {
+          size: "small",
+        },
+      });
+
+      expect(wrapper.props("size")).toBe("small");
+    });
+
+    it("should render with custom border radius", () => {
+      wrapper = mount(Selector, {
+        props: {
+          borderRadius: 8,
+        },
+      });
+
+      expect(wrapper.props("borderRadius")).toBe(8);
+    });
+
+    it("should render in disabled mode", () => {
+      wrapper = mount(Selector, {
+        props: {
+          disabled: true,
+        },
+      });
+
+      expect(wrapper.props("disabled")).toBe(true);
+    });
+
+    it("should render with clearable option", () => {
+      wrapper = mount(Selector, {
+        props: {
+          clearable: true,
+        },
+      });
+
+      expect(wrapper.props("clearable")).toBe(true);
+    });
+
+    it("should render in remote mode", () => {
+      wrapper = mount(Selector, {
+        props: {
+          isRemote: true,
+        },
+      });
+
+      expect(wrapper.props("isRemote")).toBe(true);
+    });
+
+    it("should render with filterable option", () => {
+      wrapper = mount(Selector, {
+        props: {
+          filterable: true,
+        },
+      });
+
+      expect(wrapper.props("filterable")).toBe(true);
+    });
+
+    it("should render with collapse tags", () => {
+      wrapper = mount(Selector, {
+        props: {
+          multiple: true,
+          collapseTags: true,
+        },
+      });
+
+      expect(wrapper.props("collapseTags")).toBe(true);
+    });
+
+    it("should render with collapse tags tooltip", () => {
+      wrapper = mount(Selector, {
+        props: {
+          multiple: true,
+          collapseTagsTooltip: true,
+        },
+      });
+
+      expect(wrapper.props("collapseTagsTooltip")).toBe(true);
+    });
+  });
+
+  describe("Component Structure", () => {
+    it("should have correct template structure", () => {
+      wrapper = mount(Selector, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      expect(wrapper.exists()).toBe(true);
+      expect(wrapper.props("options")).toEqual(mockOptions);
+    });
+
+    it("should render options correctly", () => {
+      wrapper = mount(Selector, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      expect(wrapper.props("options")).toEqual(mockOptions);
+      expect(wrapper.props("options").length).toBe(3);
+    });
+  });
+
+  describe("Event Handling", () => {
+    it("should emit change event when selection changes", async () => {
+      wrapper = mount(Selector, {
+        props: {
+          options: mockOptions,
+        },
+      });
+
+      // Simulate selection change
+      await wrapper.vm.changeSelected();
+
+      expect(wrapper.emitted("change")).toBeTruthy();
+    });
+
+    it("should emit change event with correct data for single selection", 
async () => {
+      wrapper = mount(Selector, {
+        props: {
+          options: mockOptions,
+          value: "option1",
+        },
+      });
+
+      await wrapper.vm.changeSelected();
+
+      const emitted = wrapper.emitted("change");
+      expect(emitted).toBeTruthy();
+      expect(emitted[0][0]).toEqual([{ label: "Option 1", value: "option1" }]);
+    });
+
+    it("should emit change event with correct data for multiple selection", 
async () => {
+      wrapper = mount(Selector, {
+        props: {
+          options: mockOptions,
+          multiple: true,
+          value: ["option1", "option2"],
+        },
+      });
+
+      await wrapper.vm.changeSelected();
+
+      const emitted = wrapper.emitted("change");
+      expect(emitted).toBeTruthy();
+      expect(emitted[0][0]).toEqual([
+        { label: "Option 1", value: "option1" },
+        { label: "Option 2", value: "option2" },
+      ]);
+    });
+
+    it("should emit query event in remote mode", async () => {
+      wrapper = mount(Selector, {
+        props: {
+          isRemote: true,
+        },
+      });
+
+      await wrapper.vm.remoteMethod("test query");
+
+      const emitted = wrapper.emitted("query");
+      expect(emitted).toBeTruthy();
+      expect(emitted[0][0]).toBe("test query");
+    });
+
+    it("should not emit query event when not in remote mode", async () => {
+      wrapper = mount(Selector, {
+        props: {
+          isRemote: false,
+        },
+      });
+
+      await wrapper.vm.remoteMethod("test query");
+
+      expect(wrapper.emitted("query")).toBeFalsy();
+    });
+  });
+
+  describe("Watchers", () => {
+    it("should update selected value when props.value changes", async () => {
+      wrapper = mount(Selector, {
+        props: {
+          options: mockOptions,
+          value: "option1",
+        },
+      });
+
+      expect(wrapper.vm.selected).toBe("option1");
+
+      await wrapper.setProps({
+        value: "option2",
+      });
+      await nextTick();
+
+      expect(wrapper.vm.selected).toBe("option2");
+    });
+
+    it("should update selected value for multiple selection", async () => {
+      wrapper = mount(Selector, {
+        props: {
+          options: mockOptions,
+          multiple: true,
+          value: ["option1"],
+        },
+      });
+
+      expect(wrapper.vm.selected).toEqual(["option1"]);
+
+      await wrapper.setProps({
+        value: ["option1", "option2"],
+      });
+      await nextTick();
+
+      expect(wrapper.vm.selected).toEqual(["option1", "option2"]);
+    });
+  });
+
+  describe("Edge Cases", () => {
+    it("should handle empty options array", () => {
+      wrapper = mount(Selector, {
+        props: {
+          options: [],
+        },
+      });
+
+      expect(wrapper.props("options")).toEqual([]);
+    });
+
+    it("should handle undefined value", () => {
+      wrapper = mount(Selector, {
+        props: {
+          options: mockOptions,
+          value: undefined,
+        },
+      });
+
+      // The component uses default value [] when value is undefined
+      expect(wrapper.vm.selected).toEqual([]);
+    });
+
+    it("should handle null value", () => {
+      wrapper = mount(Selector, {
+        props: {
+          options: mockOptions,
+          value: null,
+        },
+      });
+
+      expect(wrapper.vm.selected).toBeNull();
+    });
+
+    it("should handle changeSelected with no matching options", async () => {
+      wrapper = mount(Selector, {
+        props: {
+          options: mockOptions,
+          value: "nonexistent",
+        },
+      });
+
+      await wrapper.vm.changeSelected();
+
+      const emitted = wrapper.emitted("change");
+      expect(emitted).toBeTruthy();
+      expect(emitted[0][0]).toEqual([]);
+    });
+  });
+
+  describe("Integration", () => {
+    it("should work with all props combined", () => {
+      wrapper = mount(Selector, {
+        props: {
+          options: mockOptions,
+          value: "option1",
+          size: "small",
+          placeholder: "Select option",
+          borderRadius: 5,
+          multiple: false,
+          disabled: false,
+          clearable: true,
+          isRemote: false,
+          filterable: true,
+          collapseTags: false,
+          collapseTagsTooltip: false,
+        },
+      });
+
+      expect(wrapper.exists()).toBe(true);
+      expect(wrapper.vm.selected).toBe("option1");
+      expect(wrapper.props("options")).toEqual(mockOptions);
+    });
+
+    it("should handle complex multiple selection scenario", async () => {
+      wrapper = mount(Selector, {
+        props: {
+          options: mockOptions,
+          multiple: true,
+          value: ["option1"],
+          clearable: true,
+          filterable: true,
+        },
+      });
+
+      expect(wrapper.vm.selected).toEqual(["option1"]);
+
+      await wrapper.setProps({
+        value: ["option1", "option2"],
+      });
+      await nextTick();
+
+      expect(wrapper.vm.selected).toEqual(["option1", "option2"]);
+
+      await wrapper.vm.changeSelected();
+
+      const emitted = wrapper.emitted("change");
+      expect(emitted).toBeTruthy();
+      expect(emitted[0][0]).toEqual([
+        { label: "Option 1", value: "option1" },
+        { label: "Option 2", value: "option2" },
+      ]);
+    });
+  });
+});
diff --git a/src/components/__tests__/TimePicker.spec.ts 
b/src/components/__tests__/TimePicker.spec.ts
new file mode 100644
index 00000000..7639bf91
--- /dev/null
+++ b/src/components/__tests__/TimePicker.spec.ts
@@ -0,0 +1,921 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * 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 { nextTick } from "vue";
+import TimePicker from "../TimePicker.vue";
+
+// Mock vue-i18n
+vi.mock("vue-i18n", () => ({
+  useI18n: () => ({
+    t: (key: string) => {
+      const translations: Record<string, string> = {
+        hourTip: "Select Hour",
+        minuteTip: "Select Minute",
+        secondTip: "Select Second",
+        yearSuffix: "",
+        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",
+        cancel: "Cancel",
+        confirm: "Confirm",
+        quarterHourCutTip: "Quarter Hour",
+        halfHourCutTip: "Half Hour",
+        hourCutTip: "Hour",
+        dayCutTip: "Day",
+        weekCutTip: "Week",
+        monthCutTip: "Month",
+      };
+      return translations[key] || key;
+    },
+  }),
+}));
+
+// Mock useTimeout hook
+vi.mock("@/hooks/useTimeout", () => ({
+  useTimeoutFn: vi.fn((callback: Function, delay: number) => {
+    setTimeout(callback, delay);
+  }),
+}));
+
+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();
+  });
+
+  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);
+    });
+
+    it("should render with custom position", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          position: "top",
+        },
+      });
+
+      expect(wrapper.props("position")).toBe("top");
+    });
+
+    it("should render with custom type", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          type: "inline",
+        },
+      });
+
+      expect(wrapper.props("type")).toBe("inline");
+    });
+
+    it("should render with custom range separator", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          rangeSeparator: "to",
+        },
+      });
+
+      expect(wrapper.props("rangeSeparator")).toBe("to");
+    });
+
+    it("should render with clearable prop", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          clearable: true,
+        },
+      });
+
+      expect(wrapper.props("clearable")).toBe(true);
+    });
+
+    it("should render with disabled prop", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          disabled: true,
+        },
+      });
+
+      expect(wrapper.props("disabled")).toBe(true);
+    });
+
+    it("should render with custom placeholder", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          placeholder: "Select date",
+        },
+      });
+
+      expect(wrapper.props("placeholder")).toBe("Select date");
+    });
+
+    it("should render with custom format", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          format: "YYYY-MM-DD HH:mm:ss",
+        },
+      });
+
+      expect(wrapper.props("format")).toBe("YYYY-MM-DD HH:mm:ss");
+    });
+
+    it("should render with showButtons prop", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          showButtons: true,
+        },
+      });
+
+      expect(wrapper.props("showButtons")).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,
+        },
+      });
+
+      expect(wrapper.props("maxRange")).toEqual(maxRange);
+    });
+
+    it("should render with disabledDate function", () => {
+      const disabledDate = vi.fn(() => false);
+      wrapper = mount(TimePicker, {
+        props: {
+          disabledDate,
+        },
+      });
+
+      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;
+      expect(wrapper.vm.range).toBe(true);
+    });
+
+    it("should format text correctly for single date", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          value: mockDate,
+        },
+      });
+      const formattedText = wrapper.vm.text;
+      expect(formattedText).toContain("2024-01-15");
+    });
+
+    it("should format text correctly for date range", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          value: mockDateRange,
+        },
+      });
+      const formattedText = wrapper.vm.text;
+      expect(formattedText).toContain("2024-01-01");
+      expect(formattedText).toContain("2024-01-31");
+      expect(formattedText).toContain("~");
+    });
+
+    it("should format text with custom range separator", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          value: mockDateRange,
+          rangeSeparator: "to",
+        },
+      });
+      const formattedText = wrapper.vm.text;
+      expect(formattedText).toContain("to");
+    });
+
+    it("should return empty text for empty value", () => {
+      wrapper.vm.dates = [];
+      expect(wrapper.vm.text).toBe("");
+    });
+
+    it("should get correct value for single date", () => {
+      wrapper.vm.dates = [mockDate];
+      const result = wrapper.vm.get();
+      expect(result).toBe(mockDate);
+    });
+
+    it("should get correct value for date range", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          value: mockDateRange,
+        },
+      });
+      const result = wrapper.vm.get();
+      expect(result).toEqual(wrapper.vm.dates);
+    });
+  });
+
+  describe("Methods", () => {
+    beforeEach(() => {
+      wrapper = mount(TimePicker);
+    });
+
+    it("should handle clear action", () => {
+      wrapper.vm.dates = [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.vm.cls();
+
+      expect(wrapper.emitted("clear")).toBeTruthy();
+      expect(wrapper.emitted("input")[0]).toEqual([[]]);
+    });
+
+    it("should validate input correctly for array", () => {
+      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", () => {
+      const result = wrapper.vm.vi(mockDate);
+      expect(result).toHaveLength(1);
+      expect(result[0]).toBeInstanceOf(Date);
+    });
+
+    it("should validate input correctly for empty value", () => {
+      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.vm.ok(false);
+
+      expect(wrapper.emitted("input")).toBeTruthy();
+    });
+
+    it("should handle ok event with leaveOpened", () => {
+      wrapper.vm.dates = [mockDate];
+      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.vm.setDates(newDate, "right");
+
+      expect(wrapper.vm.dates[1]).toBe(newDate);
+    });
+
+    it("should handle setDates for left position", () => {
+      wrapper.vm.dates = [mockDate, mockDate];
+      const newDate = new Date(2024, 1, 1);
+      wrapper.vm.setDates(newDate, "left");
+
+      expect(wrapper.vm.dates[0]).toBe(newDate);
+    });
+
+    it("should handle document click", () => {
+      const mockEvent = {
+        target: document.createElement("div"),
+      } as unknown as MouseEvent;
+      wrapper.vm.datepicker = {
+        contains: vi.fn(() => true),
+      };
+      wrapper.vm.dc(mockEvent);
+
+      expect(wrapper.vm.show).toBe(true);
+    });
+
+    it("should handle document click outside", () => {
+      const mockEvent = {
+        target: document.createElement("div"),
+      } as unknown as MouseEvent;
+      wrapper.vm.datepicker = {
+        contains: vi.fn(() => false),
+      };
+      wrapper.vm.dc(mockEvent);
+
+      expect(wrapper.vm.show).toBe(false);
+    });
+
+    it("should handle document click when disabled", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          disabled: true,
+        },
+      });
+      const mockEvent = {
+        target: document.createElement("div"),
+      } as unknown as MouseEvent;
+      wrapper.vm.datepicker = {
+        contains: vi.fn(() => true),
+      };
+      wrapper.vm.dc(mockEvent);
+
+      expect(wrapper.vm.show).toBe(false);
+    });
+  });
+
+  describe("Quick Pick Functionality", () => {
+    beforeEach(() => {
+      wrapper = mount(TimePicker);
+    });
+
+    it("should handle quarter hour quick pick", () => {
+      wrapper.vm.quickPick("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.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.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.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.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.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");
+
+      // The quickPick function always sets dates to [start, end] regardless 
of type
+      expect(wrapper.vm.dates).toHaveLength(2);
+      expect(wrapper.vm.dates[0]).toBeInstanceOf(Date);
+      expect(wrapper.vm.dates[1]).toBeInstanceOf(Date);
+    });
+  });
+
+  describe("Button Actions", () => {
+    beforeEach(() => {
+      wrapper = mount(TimePicker, {
+        props: {
+          showButtons: true,
+        },
+      });
+    });
+
+    it("should handle submit action", () => {
+      wrapper.vm.dates = [mockDate];
+      wrapper.vm.submit();
+
+      expect(wrapper.emitted("confirm")).toBeTruthy();
+      expect(wrapper.vm.show).toBe(false);
+    });
+
+    it("should handle cancel action", () => {
+      wrapper.vm.cancel();
+
+      expect(wrapper.emitted("cancel")).toBeTruthy();
+      expect(wrapper.vm.show).toBe(false);
+    });
+  });
+
+  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();
+    });
+
+    it("should render input with custom class", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          inputClass: "custom-input",
+        },
+      });
+
+      const input = wrapper.find("input");
+      expect(input.classes()).toContain("custom-input");
+    });
+
+    it("should render input with placeholder", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          placeholder: "Select date",
+        },
+      });
+
+      const input = wrapper.find("input");
+      expect(input.attributes("placeholder")).toBe("Select date");
+    });
+
+    it("should render disabled input", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          disabled: true,
+        },
+      });
+
+      const input = wrapper.find("input");
+      expect(input.attributes("disabled")).toBeDefined();
+    });
+
+    it("should render clear button when clearable and has value", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          clearable: true,
+        },
+      });
+      wrapper.vm.dates = [mockDate];
+
+      const clearButton = wrapper.find(".datepicker-close");
+      expect(clearButton.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
+    });
+
+    it("should render popup with correct position class", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          position: "top",
+          type: "inline",
+        },
+      });
+
+      const popup = wrapper.find(".datepicker-popup");
+      expect(popup.classes()).toContain("top");
+    });
+
+    it("should render inline popup", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          type: "inline",
+        },
+      });
+
+      const popup = wrapper.find(".datepicker-popup");
+      expect(popup.classes()).toContain("datepicker-inline");
+    });
+
+    it("should render sidebar for range mode", async () => {
+      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 sidebar = wrapper.find(".datepicker-popup__sidebar");
+      expect(sidebar.exists()).toBe(true);
+    });
+
+    it("should render quick pick buttons", async () => {
+      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 buttons = wrapper.findAll(".datepicker-popup__shortcut");
+      expect(buttons).toHaveLength(6);
+    });
+
+    it("should render DateCalendar components", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          type: "inline",
+        },
+      });
+
+      const calendars = wrapper.findAllComponents({ name: "DateCalendar" });
+      expect(calendars).toHaveLength(1);
+    });
+
+    it("should render two DateCalendar components for range", async () => {
+      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);
+    });
+
+    it("should render buttons when showButtons is true", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          showButtons: true,
+          type: "inline",
+        },
+      });
+
+      const buttons = wrapper.find(".datepicker__buttons");
+      expect(buttons.exists()).toBe(true);
+    });
+
+    it("should not render buttons when showButtons is false", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          showButtons: false,
+        },
+      });
+      wrapper.vm.show = true;
+
+      const buttons = wrapper.find(".datepicker__buttons");
+      expect(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();
+
+      expect(wrapper.emitted("clear")).toBeTruthy();
+    });
+
+    it("should handle DateCalendar ok event", async () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          type: "inline",
+        },
+      });
+      const calendar = wrapper.findComponent({ name: "DateCalendar" });
+
+      await calendar.vm.$emit("ok", false);
+      await nextTick();
+
+      expect(wrapper.emitted("input")).toBeTruthy();
+    });
+
+    it("should handle DateCalendar setDates event", async () => {
+      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);
+    });
+
+    it("should handle submit button click", async () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          showButtons: true,
+          type: "inline",
+        },
+      });
+      wrapper.vm.dates = [mockDate];
+
+      const submitButton = wrapper.find(".datepicker__button-select");
+      await submitButton.trigger("click");
+      await nextTick();
+
+      expect(wrapper.emitted("confirm")).toBeTruthy();
+    });
+
+    it("should handle cancel button click", async () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          showButtons: true,
+          type: "inline",
+        },
+      });
+
+      const cancelButton = wrapper.find(".datepicker__button-cancel");
+      await cancelButton.trigger("click");
+      await nextTick();
+
+      expect(wrapper.emitted("cancel")).toBeTruthy();
+    });
+
+    it("should handle quick pick button clicks", async () => {
+      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();
+
+      // Check if range mode is active
+      if (wrapper.vm.range) {
+        const quarterButton = wrapper.find(".datepicker-popup__shortcut");
+        await quarterButton.trigger("click");
+        await nextTick();
+
+        expect(wrapper.emitted("input")).toBeTruthy();
+      } else {
+        // If not in range mode, test the quickPick method directly
+        wrapper.vm.quickPick("quarter");
+        expect(wrapper.emitted("input")).toBeTruthy();
+      }
+    });
+  });
+
+  describe("Lifecycle", () => {
+    it("should add document event listener on mount", () => {
+      wrapper = mount(TimePicker);
+
+      expect(document.addEventListener).toHaveBeenCalledWith("click", 
expect.any(Function), true);
+    });
+
+    it("should remove document event listener on unmount", () => {
+      wrapper = mount(TimePicker);
+      wrapper.unmount();
+
+      expect(document.removeEventListener).toHaveBeenCalledWith("click", 
expect.any(Function), true);
+    });
+
+    it("should initialize dates from props value", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          value: mockDate,
+        },
+      });
+
+      expect(wrapper.vm.dates).toHaveLength(1);
+      expect(wrapper.vm.dates[0]).toBeInstanceOf(Date);
+    });
+
+    it("should initialize dates from array value", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          value: mockDateRange,
+        },
+      });
+
+      expect(wrapper.vm.dates).toHaveLength(2);
+      expect(wrapper.vm.dates[0]).toBeInstanceOf(Date);
+      expect(wrapper.vm.dates[1]).toBeInstanceOf(Date);
+    });
+
+    it("should watch for value prop changes", async () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          value: mockDate,
+        },
+      });
+
+      const newDate = new Date(2025, 5, 20);
+      await wrapper.setProps({ value: newDate });
+      await nextTick();
+
+      expect(wrapper.vm.dates[0]).toEqual(newDate);
+    });
+  });
+
+  describe("Edge Cases", () => {
+    it("should handle null value", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          value: null as any,
+        },
+      });
+
+      expect(wrapper.vm.dates).toHaveLength(1);
+      expect(wrapper.vm.dates[0]).toBeInstanceOf(Date);
+    });
+
+    it("should handle undefined value", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          value: undefined,
+        },
+      });
+
+      expect(wrapper.vm.dates).toHaveLength(1);
+      expect(wrapper.vm.dates[0]).toBeInstanceOf(Date);
+    });
+
+    it("should handle empty array value", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          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", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          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);
+    });
+
+    it("should handle string value", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          value: "2024-01-15",
+        },
+      });
+
+      expect(wrapper.vm.dates).toHaveLength(1);
+      expect(wrapper.vm.dates[0]).toBeInstanceOf(Date);
+    });
+
+    it("should handle invalid date string", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          value: "invalid-date",
+        },
+      });
+
+      expect(wrapper.vm.dates).toHaveLength(1);
+      expect(wrapper.vm.dates[0]).toBeInstanceOf(Date);
+    });
+  });
+
+  describe("Accessibility", () => {
+    it("should have proper tabindex on popup", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          type: "inline",
+        },
+      });
+
+      const popup = wrapper.find(".datepicker-popup");
+      expect(popup.attributes("tabindex")).toBe("-1");
+    });
+
+    it("should have proper button types", () => {
+      wrapper = mount(TimePicker, {
+        props: {
+          showButtons: true,
+          type: "inline",
+        },
+      });
+
+      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;
+
+      const quickPickButtons = wrapper.findAll(".datepicker-popup__shortcut");
+      quickPickButtons.forEach((button: Recordable) => {
+        expect(button.attributes("type")).toBe("button");
+      });
+    });
+  });
+
+  describe("Internationalization", () => {
+    it("should use i18n translations", () => {
+      wrapper = mount(TimePicker);
+
+      expect(wrapper.vm.local.cancelTip).toBe("Cancel");
+      expect(wrapper.vm.local.submitTip).toBe("Confirm");
+      expect(wrapper.vm.local.quarterHourCutTip).toBe("Quarter Hour");
+      expect(wrapper.vm.local.halfHourCutTip).toBe("Half Hour");
+      expect(wrapper.vm.local.hourCutTip).toBe("Hour");
+      expect(wrapper.vm.local.dayCutTip).toBe("Day");
+      expect(wrapper.vm.local.weekCutTip).toBe("Week");
+      expect(wrapper.vm.local.monthCutTip).toBe("Month");
+    });
+
+    it("should handle month names correctly", () => {
+      wrapper = mount(TimePicker);
+
+      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/store/modules/demand-log.ts b/src/store/modules/demand-log.ts
index 9611715c..16f93c61 100644
--- a/src/store/modules/demand-log.ts
+++ b/src/store/modules/demand-log.ts
@@ -59,6 +59,9 @@ export const demandLogStore = defineStore({
     },
     async getInstances(id: string) {
       const serviceId = this.selectorStore.currentService ? 
this.selectorStore.currentService.id : id;
+      if (!serviceId) {
+        return new Promise((resolve) => resolve({ errors: "Service ID is 
required" }));
+      }
       const response = await graphql.query("queryInstances").params({
         serviceId,
         duration: useAppStoreWithOut().durationTime,
diff --git a/src/store/modules/event.ts b/src/store/modules/event.ts
index 926123d4..4e90a723 100644
--- a/src/store/modules/event.ts
+++ b/src/store/modules/event.ts
@@ -46,6 +46,10 @@ export const eventStore = defineStore({
     },
     async getInstances() {
       const serviceId = useSelectorStore().currentService ? 
useSelectorStore().currentService.id : "";
+
+      if (!serviceId) {
+        return new Promise((resolve) => resolve({ errors: "Service ID is 
required" }));
+      }
       const response = await graphql.query("queryInstances").params({
         serviceId,
         duration: useAppStoreWithOut().durationTime,
@@ -60,7 +64,7 @@ export const eventStore = defineStore({
     async getEndpoints(keyword: string) {
       const serviceId = useSelectorStore().currentService ? 
useSelectorStore().currentService.id : "";
       if (!serviceId) {
-        return;
+        return new Promise((resolve) => resolve({ errors: "Service ID is 
required" }));
       }
       const response = await graphql.query("queryEndpoints").params({
         serviceId,
diff --git a/src/store/modules/log.ts b/src/store/modules/log.ts
index 2bb56ea5..f021df5c 100644
--- a/src/store/modules/log.ts
+++ b/src/store/modules/log.ts
@@ -74,6 +74,9 @@ export const logStore = defineStore({
     },
     async getInstances(id: string) {
       const serviceId = this.selectorStore.currentService ? 
this.selectorStore.currentService.id : id;
+      if (!serviceId) {
+        return new Promise((resolve) => resolve({ errors: "Service ID is 
required" }));
+      }
       const response = await graphql.query("queryInstances").params({
         serviceId,
         duration: useAppStoreWithOut().durationTime,
diff --git a/src/store/modules/selectors.ts b/src/store/modules/selectors.ts
index 71e2dbac..8165445f 100644
--- a/src/store/modules/selectors.ts
+++ b/src/store/modules/selectors.ts
@@ -91,7 +91,7 @@ export const selectorStore = defineStore({
     async getServiceInstances(param?: { serviceId: string; isRelation: boolean 
}) {
       const serviceId = param ? param.serviceId : this.currentService?.id;
       if (!serviceId) {
-        return null;
+        return new Promise((resolve) => resolve({ errors: "Service ID is 
required" }));
       }
       const resp = await graphql.query("queryInstances").params({
         serviceId,
@@ -130,7 +130,7 @@ export const selectorStore = defineStore({
       }
       const serviceId = params.serviceId || this.currentService?.id;
       if (!serviceId) {
-        return null;
+        return new Promise((resolve) => resolve({ errors: "Service ID is 
required" }));
       }
       const res = await graphql.query("queryEndpoints").params({
         serviceId,
diff --git a/src/store/modules/trace.ts b/src/store/modules/trace.ts
index 9c474dd2..fe756874 100644
--- a/src/store/modules/trace.ts
+++ b/src/store/modules/trace.ts
@@ -123,6 +123,9 @@ export const traceStore = defineStore({
     },
     async getInstances(id: string) {
       const serviceId = this.selectorStore.currentService ? 
this.selectorStore.currentService.id : id;
+      if (!serviceId) {
+        return new Promise((resolve) => resolve({ errors: "Service ID is 
required" }));
+      }
       const response = await graphql.query("queryInstances").params({
         serviceId: serviceId,
         duration: useAppStoreWithOut().durationTime,
@@ -136,6 +139,9 @@ export const traceStore = defineStore({
     },
     async getEndpoints(id: string, keyword?: string) {
       const serviceId = this.selectorStore.currentService ? 
this.selectorStore.currentService.id : id;
+      if (!serviceId) {
+        return new Promise((resolve) => resolve({ errors: "Service ID is 
required" }));
+      }
       const response = await graphql.query("queryEndpoints").params({
         serviceId,
         duration: useAppStoreWithOut().durationTime,
diff --git a/src/types/components.d.ts b/src/types/components.d.ts
index 56f60832..7fcd7040 100644
--- a/src/types/components.d.ts
+++ b/src/types/components.d.ts
@@ -47,6 +47,7 @@ declare module 'vue' {
     ElTag: typeof import('element-plus/es')['ElTag']
     ElTooltip: typeof import('element-plus/es')['ElTooltip']
     Graph: typeof import('./../components/Graph/Graph.vue')['default']
+    GraphSelector: typeof 
import('./../components/Graph/GraphSelector.vue')['default']
     Icon: typeof import('./../components/Icon.vue')['default']
     Legend: typeof import('./../components/Graph/Legend.vue')['default']
     Radio: typeof import('./../components/Radio.vue')['default']
diff --git 
a/src/views/dashboard/related/continuous-profiling/components/PolicyList.vue 
b/src/views/dashboard/related/continuous-profiling/components/PolicyList.vue
index 726ae130..ca8aba4f 100644
--- a/src/views/dashboard/related/continuous-profiling/components/PolicyList.vue
+++ b/src/views/dashboard/related/continuous-profiling/components/PolicyList.vue
@@ -105,7 +105,7 @@ limitations under the License. -->
   ) {
     const serviceId = (selectorStore.currentService && 
selectorStore.currentService.id) || "";
     if (!serviceId) {
-      return ElMessage.error("No Service ID");
+      return ElMessage.error("Service ID is required");
     }
     const res = await 
continousProfilingStore.setContinuousProfilingPolicy(serviceId, targets);
     if (res.errors) {
diff --git a/src/views/dashboard/related/trace/utils/d3-trace-list.ts 
b/src/views/dashboard/related/trace/utils/d3-trace-list.ts
index 8a8ff7d6..c97a7433 100644
--- a/src/views/dashboard/related/trace/utils/d3-trace-list.ts
+++ b/src/views/dashboard/related/trace/utils/d3-trace-list.ts
@@ -174,6 +174,7 @@ export default class ListGraph {
           .style("display", "block")
           .style("left", `${offsetX + 30}px`)
           .style("top", `${offsetY + 40}px`);
+        t.selectedNode?.classed("highlighted", false);
         t.selectedNode = d3.select(this);
         if (t.handleSelectSpan) {
           t.handleSelectSpan(d);
diff --git a/src/views/dashboard/related/trace/utils/d3-trace-tree.ts 
b/src/views/dashboard/related/trace/utils/d3-trace-tree.ts
index 48c723d0..f9e61960 100644
--- a/src/views/dashboard/related/trace/utils/d3-trace-tree.ts
+++ b/src/views/dashboard/related/trace/utils/d3-trace-tree.ts
@@ -315,6 +315,7 @@ export default class TraceMap {
           .style("display", "block")
           .style("left", `${offsetX + 30}px`)
           .style("top", `${offsetY + 40}px`);
+        t.selectedNode?.classed("highlighted", false);
         t.selectedNode = d3.select(this.parentNode);
         if (t.handleSelectSpan) {
           t.handleSelectSpan(d);

Reply via email to