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