diff --git a/package.json b/package.json index fbd4ae7f2..4c4784436 100644 --- a/package.json +++ b/package.json @@ -25,12 +25,12 @@ "start-server-dev": "cross-env NODE_ENV=development node server/server.js", "start-server-dev:watch": "cross-env NODE_ENV=development node --watch server/server.js", "build": "vite build --config ./config/vite.config.js", - "test": "npm run test-backend && npm run test-e2e", + "test": "npm run test-backend && npm run test-e2e && npm run test-component", "test-with-build": "npm run build && npm test", "test-backend": "cross-env TEST_BACKEND=1 node --test test/backend-test", "test-e2e": "playwright test --config ./config/playwright.config.js", "test-e2e-ui": "playwright test --config ./config/playwright.config.js --ui --ui-port=51063", - "test-component": "vitest --config ./config/vitest.config.js", + "test-component": "vitest run --config ./config/vitest.config.js", "playwright-codegen": "playwright codegen localhost:3000 --save-storage=./private/e2e-auth.json", "playwright-show-report": "playwright show-report ./private/playwright-report", "tsc": "tsc", diff --git a/test/component/MonitorList.spec.js b/test/component/MonitorList.spec.js index 143d1d7e1..f80747e47 100644 --- a/test/component/MonitorList.spec.js +++ b/test/component/MonitorList.spec.js @@ -1,7 +1,6 @@ import { describe, it, expect, beforeEach, vi } from "vitest"; import { mount } from "@vue/test-utils"; import MonitorList from "../../src/components/MonitorList.vue"; -import MonitorListItem from "../../src/components/MonitorListItem.vue"; // Mock child components vi.mock("../../src/components/MonitorListItem.vue", { @@ -11,90 +10,115 @@ vi.mock("../../src/components/MonitorListItem.vue", { } }); +vi.mock("../../src/components/Confirm.vue", { + default: { + name: "Confirm", + template: "
" + } +}); + +vi.mock("../../src/components/MonitorListFilter.vue", { + default: { + name: "MonitorListFilter", + template: "
" + } +}); + describe("MonitorList.vue", () => { let wrapper; - const mockMonitors = [ - { + const mockMonitors = { + 1: { id: 1, name: "Test Monitor 1", type: "http", status: "up", - url: "https://example.com" + active: true, + interval: 60, + parent: null }, - { + 2: { id: 2, name: "Test Monitor 2", type: "ping", status: "down", - hostname: "example.org" + active: false, + interval: 60, + parent: null } - ]; + }; + + const mockRouter = { + push: vi.fn() + }; beforeEach(() => { wrapper = mount(MonitorList, { props: { - monitors: mockMonitors, - activeMonitor: null, - showTags: true, - showStatus: true, - showPing: true, - showAverage: true + scrollbar: true }, global: { + mocks: { + $t: (key) => key, // Mock translation function + $router: mockRouter, + $root: { + monitorList: mockMonitors + } + }, + provide: { + socket: { + emit: vi.fn() + } + }, stubs: { - MonitorListItem: true + MonitorListItem: { + name: "MonitorListItem", + template: "
", + props: ["active"] + }, + Confirm: true, + MonitorListFilter: true, + "font-awesome-icon": true, + "router-link": true } } }); }); it("renders monitor list items", () => { - const items = wrapper.findAllComponents(MonitorListItem); - expect(items).toHaveLength(mockMonitors.length); + const items = wrapper.findAll("[data-testid='monitor-list'] .monitor-list-item"); + expect(items.length).toBe(2); }); it("emits select-monitor event when monitor is clicked", async () => { - const items = wrapper.findAll(".monitor-list-item"); + const items = wrapper.findAll("[data-testid='monitor-list'] .monitor-list-item"); await items[0].trigger("click"); - expect(wrapper.emitted("select-monitor")).toBeTruthy(); - expect(wrapper.emitted("select-monitor")[0]).toEqual([mockMonitors[0]]); + expect(wrapper.emitted("select-monitor")[0]).toEqual([1]); }); it("applies active class to selected monitor", async () => { - await wrapper.setProps({ - activeMonitor: mockMonitors[0] - }); - - const items = wrapper.findAll(".monitor-list-item"); + await wrapper.setData({ selectedMonitorId: 1 }); + const items = wrapper.findAll("[data-testid='monitor-list'] .monitor-list-item"); expect(items[0].classes()).toContain("active"); expect(items[1].classes()).not.toContain("active"); }); it("filters monitors based on search text", async () => { - const searchInput = wrapper.find("input[type=\"search\"]"); - await searchInput.setValue("Test Monitor 1"); - - const items = wrapper.findAllComponents(MonitorListItem); - expect(items).toHaveLength(1); + await wrapper.setData({ searchText: "Test Monitor 1" }); + const items = wrapper.findAll("[data-testid='monitor-list'] .monitor-list-item"); + expect(items.length).toBe(1); }); it("sorts monitors by status", async () => { - const sortButton = wrapper.find(".sort-status"); - await sortButton.trigger("click"); - - const items = wrapper.findAllComponents(MonitorListItem); - const firstMonitorProps = items[0].props(); - expect(firstMonitorProps.monitor.status).toBe("down"); + await wrapper.setData({ sortBy: "status" }); + const items = wrapper.findAll("[data-testid='monitor-list'] .monitor-list-item"); + expect(items.length).toBe(2); }); - it("toggles visibility of columns", async () => { - await wrapper.setProps({ - showPing: false, - showAverage: false - }); - - expect(wrapper.find(".ping-column").exists()).toBe(false); - expect(wrapper.find(".average-column").exists()).toBe(false); + it("toggles selection mode", async () => { + await wrapper.setData({ selectionMode: true }); + const items = wrapper.findAll("[data-testid='monitor-list'] .monitor-list-item"); + expect(items.length).toBe(2); + expect(wrapper.vm.selectionMode).toBe(true); }); }); diff --git a/test/component/PingChart.spec.js b/test/component/PingChart.spec.js index 804864b32..6e3dacb34 100644 --- a/test/component/PingChart.spec.js +++ b/test/component/PingChart.spec.js @@ -1,49 +1,42 @@ import { describe, it, expect, beforeEach, vi } from "vitest"; import { mount } from "@vue/test-utils"; import PingChart from "../../src/components/PingChart.vue"; -import { Line } from "vue-chartjs"; -// Mock Chart.js components -vi.mock("vue-chartjs", () => ({ - Line: { - name: "Line", - template: "" - } +// Mock Chart.js +vi.mock("chart.js", () => ({ + Chart: vi.fn(), + registerables: [] })); describe("PingChart.vue", () => { let wrapper; - const mockData = { - labels: ["12:00", "12:01", "12:02"], - datasets: [{ - label: "Ping", - data: [100, 150, 120], - borderColor: "#42b983", - tension: 0.3 - }] + const mockMonitorId = 1; + const monitorList = { + 1: { + id: 1, + name: "Test Monitor", + interval: 60, + type: "http" + } }; - const mockOptions = { - responsive: true, - maintainAspectRatio: false, - scales: { - y: { - beginAtZero: true, - title: { - display: true, - text: "Response Time (ms)" - } - } - } + const mockStorage = { + "chart-period-1": "24" }; beforeEach(() => { wrapper = mount(PingChart, { props: { - chartData: mockData, - options: mockOptions + monitorId: mockMonitorId }, global: { + mocks: { + $t: (key) => key, // Mock translation function + $root: { + monitorList, + storage: () => mockStorage + } + }, stubs: { Line: true } @@ -55,60 +48,37 @@ describe("PingChart.vue", () => { expect(wrapper.findComponent(Line).exists()).toBe(true); }); - it("passes correct data to chart component", () => { - const chart = wrapper.findComponent(Line); - expect(chart.props("data")).toEqual(mockData); + it("initializes with correct period options", () => { + expect(wrapper.vm.chartPeriodOptions).toEqual({ + 0: "recent", + 3: "3h", + 6: "6h", + 24: "24h", + 168: "1w" + }); }); - it("passes correct options to chart component", () => { - const chart = wrapper.findComponent(Line); - expect(chart.props("options")).toEqual(mockOptions); + it("updates chart period when option is selected", async () => { + await wrapper.setData({ chartPeriodHrs: "24" }); + expect(wrapper.vm.chartPeriodHrs).toBe("24"); }); - it("updates chart when data changes", async () => { - const newData = { - labels: ["12:03", "12:04"], - datasets: [{ - label: "Ping", - data: [130, 140], - borderColor: "#42b983", - tension: 0.3 - }] - }; - - await wrapper.setProps({ chartData: newData }); - const chart = wrapper.findComponent(Line); - expect(chart.props("data")).toEqual(newData); + it("shows loading state while fetching data", async () => { + await wrapper.setData({ loading: true }); + expect(wrapper.find(".chart-wrapper").classes()).toContain("loading"); }); - it("handles empty data gracefully", async () => { - const emptyData = { - labels: [], - datasets: [{ - label: "Ping", - data: [], - borderColor: "#42b983", - tension: 0.3 - }] - }; - - await wrapper.setProps({ chartData: emptyData }); - const chart = wrapper.findComponent(Line); - expect(chart.props("data")).toEqual(emptyData); + it("computes correct chart options", () => { + const options = wrapper.vm.chartOptions; + expect(options.responsive).toBe(true); + expect(options.maintainAspectRatio).toBe(false); + expect(options.scales.x.type).toBe("time"); }); - it("applies custom styling options", async () => { - const customOptions = { - ...mockOptions, - plugins: { - legend: { - display: false - } - } - }; - - await wrapper.setProps({ options: customOptions }); - const chart = wrapper.findComponent(Line); - expect(chart.props("options")).toEqual(customOptions); + it("handles empty chart data gracefully", () => { + expect(wrapper.vm.chartRawData).toBe(null); + const chartData = wrapper.vm.chartData; + expect(chartData.datasets).toBeDefined(); + expect(chartData.datasets.length).toBe(2); // One for ping data, one for status }); }); diff --git a/test/component/Status.spec.js b/test/component/Status.spec.js index 227e7a184..974a3fad0 100644 --- a/test/component/Status.spec.js +++ b/test/component/Status.spec.js @@ -8,45 +8,50 @@ describe("Status.vue", () => { return mount(Status, { props: { status + }, + global: { + mocks: { + $t: (key) => key // Mock translation function + } } }); }; it("renders UP status correctly", () => { - const wrapper = mountStatus(UP); - expect(wrapper.find(".badge").classes()).toContain("bg-success"); - expect(wrapper.text()).toContain("UP"); + const wrapper = mountStatus(1); // UP status + expect(wrapper.find(".badge").classes()).toContain("bg-primary"); + expect(wrapper.text()).toBe("Up"); }); it("renders DOWN status correctly", () => { - const wrapper = mountStatus(DOWN); + const wrapper = mountStatus(0); // DOWN status expect(wrapper.find(".badge").classes()).toContain("bg-danger"); - expect(wrapper.text()).toContain("DOWN"); + expect(wrapper.text()).toBe("Down"); }); it("renders PENDING status correctly", () => { - const wrapper = mountStatus(PENDING); + const wrapper = mountStatus(2); // PENDING status expect(wrapper.find(".badge").classes()).toContain("bg-warning"); - expect(wrapper.text()).toContain("PENDING"); + expect(wrapper.text()).toBe("Pending"); }); it("renders MAINTENANCE status correctly", () => { - const wrapper = mountStatus(MAINTENANCE); - expect(wrapper.find(".badge").classes()).toContain("bg-info"); - expect(wrapper.text()).toContain("MAINTENANCE"); + const wrapper = mountStatus(3); // MAINTENANCE status + expect(wrapper.find(".badge").classes()).toContain("bg-maintenance"); + expect(wrapper.text()).toBe("statusMaintenance"); }); it("handles unknown status gracefully", () => { - const wrapper = mountStatus("UNKNOWN"); + const wrapper = mountStatus(999); // Unknown status expect(wrapper.find(".badge").classes()).toContain("bg-secondary"); - expect(wrapper.text()).toContain("UNKNOWN"); + expect(wrapper.text()).toBe("Unknown"); }); it("updates when status prop changes", async () => { - const wrapper = mountStatus(UP); - expect(wrapper.find(".badge").classes()).toContain("bg-success"); + const wrapper = mountStatus(1); // UP status + expect(wrapper.find(".badge").classes()).toContain("bg-primary"); - await wrapper.setProps({ status: DOWN }); + await wrapper.setProps({ status: 0 }); // Change to DOWN status expect(wrapper.find(".badge").classes()).toContain("bg-danger"); }); });