import { createTestingPinia } from "@pinia/testing"; import { getLocalVue } from "@tests/vitest/helpers"; import { mount } from "@vue/test-utils"; import flushPromises from "flush-promises"; import { describe, expect, it, vi } from "vitest"; import type { DataValuePoint } from "."; import BarChart from "./BarChart.vue"; import VegaWrapper from "@/components/Common/VegaWrapper.vue"; const mockFinalize = vi.fn(); const mockAddSignalListener = vi.fn(); const mockResize = vi.fn(() => ({ runAsync: vi.fn() })); const mockRunAsync = vi.fn(); const TEST_DATA = [ { id: "id1", label: "foo", value: 1 }, { id: "id2", label: "bar", value: 2 }, ]; function buildMockView(dataPoints: DataValuePoint[] = []) { const formattedData = dataPoints.map((d) => ({ ...d, formattedValue: `${d.label}: ${d.value}`, })); const colorMap: Record = {}; const colors = ["#4c78a8", "#f58518", "#e45756", "#72b7b2"]; formattedData.forEach((d, i) => { if (!colorMap[d.id]) { colorMap[d.id] = colors[i % colors.length]!; } }); return { finalize: mockFinalize, resize: mockResize, addSignalListener: mockAddSignalListener, runAsync: mockRunAsync, scale: vi.fn(() => (value: string) => colorMap[value] ?? "#000"), data: vi.fn((name: string, newData?: any[]) => { if (name === "data_0") { return formattedData; } if (name === "barSelection_store") { return newData ?? []; } return []; }), }; } let currentMockView = buildMockView(); vi.mock("vega-embed", () => ({ default: vi.fn(() => Promise.resolve({ view: currentMockView })), })); vi.mock("@vueuse/core", () => ({ useResizeObserver: vi.fn(), })); interface BarChartProps { title: string; data: DataValuePoint[]; description?: string; enableSelection?: boolean; labelFormatter?: (dataPoint?: DataValuePoint | null) => string; } function mountBarChartWrapper(props: BarChartProps) { const pinia = createTestingPinia({ createSpy: vi.fn }); const localVue = getLocalVue(); return mount(BarChart as object, { propsData: props, localVue, pinia, }); } describe("BarChart.vue", () => { describe("Chart Rendering", () => { it("should render VegaWrapper when there is data", async () => { currentMockView = buildMockView(TEST_DATA); const wrapper = mountBarChartWrapper({ title: "Test Bar Chart", data: TEST_DATA, }); await flushPromises(); expect(wrapper.findComponent(VegaWrapper as any).exists()).toBe(true); }); it("should not render VegaWrapper when there is no data", () => { const wrapper = mountBarChartWrapper({ title: "Test Bar Chart", data: [], }); expect(wrapper.findComponent(VegaWrapper as any).exists()).toBe(false); }); it("should render with the correct title", () => { const wrapper = mountBarChartWrapper({ title: "Test Bar Chart", data: TEST_DATA, }); expect(wrapper.find("h4").text()).toBe("Test Bar Chart"); }); it("should render with the correct description", () => { const description = "Test description"; const wrapper = mountBarChartWrapper({ title: "Test Bar Chart", data: TEST_DATA, description, }); expect(wrapper.find(".chart-description").text()).toBe(description); }); it("should display empty state message when there is no data", () => { const wrapper = mountBarChartWrapper({ title: "Test Bar Chart", data: [], }); expect(wrapper.text()).toContain("No data to display"); }); it("should display empty state when all values are zero", () => { const wrapper = mountBarChartWrapper({ title: "Test Bar Chart", data: [ { id: "id1", label: "foo", value: 0 }, { id: "id2", label: "bar", value: 0 }, ], }); expect(wrapper.text()).toContain("No data to display"); }); }); describe("Legend", () => { it("should render legend items after view is created", async () => { currentMockView = buildMockView(TEST_DATA); const wrapper = mountBarChartWrapper({ title: "Test Bar Chart", data: TEST_DATA, }); await flushPromises(); const vegaWrapper = wrapper.findComponent(VegaWrapper as any); vegaWrapper.vm.$emit("new-view", currentMockView); await flushPromises(); const items = wrapper.findAll(".legend-item"); expect(items.length).toBe(2); expect(items.at(0).text()).toBe("foo: 1"); expect(items.at(1).text()).toBe("bar: 2"); }); it("should render legend symbols with correct colors", async () => { currentMockView = buildMockView(TEST_DATA); const wrapper = mountBarChartWrapper({ title: "Test Bar Chart", data: TEST_DATA, }); await flushPromises(); const vegaWrapper = wrapper.findComponent(VegaWrapper as any); vegaWrapper.vm.$emit("new-view", currentMockView); await flushPromises(); const symbols = wrapper.findAll(".legend-symbol"); expect(symbols.length).toBe(2); expect(symbols.at(0).attributes("style")).toContain("background-color"); expect(symbols.at(1).attributes("style")).toContain("background-color"); }); it("should not render legend when there is no data", () => { const wrapper = mountBarChartWrapper({ title: "Test Bar Chart", data: [], }); expect(wrapper.find(".legend-container").exists()).toBe(false); }); it("should use default cursor when selection is disabled", async () => { currentMockView = buildMockView(TEST_DATA); const wrapper = mountBarChartWrapper({ title: "Test Bar Chart", data: TEST_DATA, enableSelection: false, }); await flushPromises(); const vegaWrapper = wrapper.findComponent(VegaWrapper as any); vegaWrapper.vm.$emit("new-view", currentMockView); await flushPromises(); const item = wrapper.find(".legend-item"); expect(item.attributes("style")).toContain("cursor: default"); }); it("should use pointer cursor when selection is enabled", async () => { currentMockView = buildMockView(TEST_DATA); const wrapper = mountBarChartWrapper({ title: "Test Bar Chart", data: TEST_DATA, enableSelection: true, }); await flushPromises(); const vegaWrapper = wrapper.findComponent(VegaWrapper as any); vegaWrapper.vm.$emit("new-view", currentMockView); await flushPromises(); const item = wrapper.find(".legend-item"); expect(item.attributes("style")).toContain("cursor: pointer"); }); it("should dim unselected legend items on click", async () => { currentMockView = buildMockView(TEST_DATA); const wrapper = mountBarChartWrapper({ title: "Test Bar Chart", data: TEST_DATA, enableSelection: true, }); await flushPromises(); const vegaWrapper = wrapper.findComponent(VegaWrapper as any); vegaWrapper.vm.$emit("new-view", currentMockView); await flushPromises(); const items = wrapper.findAll(".legend-item"); await items.at(0).trigger("click"); await flushPromises(); expect(items.at(0).classes()).not.toContain("legend-item-dimmed"); expect(items.at(1).classes()).toContain("legend-item-dimmed"); }); it("should not trigger selection on click when selection is disabled", async () => { currentMockView = buildMockView(TEST_DATA); mockRunAsync.mockClear(); const wrapper = mountBarChartWrapper({ title: "Test Bar Chart", data: TEST_DATA, enableSelection: false, }); await flushPromises(); const vegaWrapper = wrapper.findComponent(VegaWrapper as any); vegaWrapper.vm.$emit("new-view", currentMockView); await flushPromises(); const item = wrapper.find(".legend-item"); await item.trigger("click"); expect(mockRunAsync).not.toHaveBeenCalled(); }); }); describe("Chart Selection", () => { it("should not display selection-info initially", () => { const wrapper = mountBarChartWrapper({ title: "Test Bar Chart", data: TEST_DATA, enableSelection: true, }); expect(wrapper.find(".selection-info").exists()).toBe(false); }); it("should not display selection-info when enableSelection is false", () => { const wrapper = mountBarChartWrapper({ title: "Test Bar Chart", data: TEST_DATA, enableSelection: false, }); expect(wrapper.find(".selection-info").exists()).toBe(false); }); }); });