mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-11-23 14:54:05 +00:00
component testing
This commit is contained in:
parent
fe09beaf6a
commit
7855dd97a5
7 changed files with 1471 additions and 2 deletions
21
config/vitest.config.js
Normal file
21
config/vitest.config.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { dirname, resolve } from 'path';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
environment: 'jsdom',
|
||||||
|
setupFiles: ['./test/component/setup.js'],
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': resolve(__dirname, '../src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
1159
package-lock.json
generated
1159
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -30,6 +30,7 @@
|
||||||
"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",
|
||||||
"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",
|
||||||
|
@ -152,12 +153,14 @@
|
||||||
"@popperjs/core": "~2.10.2",
|
"@popperjs/core": "~2.10.2",
|
||||||
"@testcontainers/hivemq": "^10.13.1",
|
"@testcontainers/hivemq": "^10.13.1",
|
||||||
"@testcontainers/rabbitmq": "^10.13.2",
|
"@testcontainers/rabbitmq": "^10.13.2",
|
||||||
|
"@testing-library/vue": "^8.1.0",
|
||||||
"@types/bootstrap": "~5.1.9",
|
"@types/bootstrap": "~5.1.9",
|
||||||
"@types/node": "^20.8.6",
|
"@types/node": "^20.8.6",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.7.5",
|
"@typescript-eslint/eslint-plugin": "^6.7.5",
|
||||||
"@typescript-eslint/parser": "^6.7.5",
|
"@typescript-eslint/parser": "^6.7.5",
|
||||||
"@vitejs/plugin-vue": "~5.0.1",
|
"@vitejs/plugin-vue": "~5.0.1",
|
||||||
"@vue/compiler-sfc": "~3.4.2",
|
"@vue/compiler-sfc": "~3.4.2",
|
||||||
|
"@vue/test-utils": "^2.4.6",
|
||||||
"@vuepic/vue-datepicker": "~3.4.8",
|
"@vuepic/vue-datepicker": "~3.4.8",
|
||||||
"aedes": "^0.46.3",
|
"aedes": "^0.46.3",
|
||||||
"bootstrap": "5.1.3",
|
"bootstrap": "5.1.3",
|
||||||
|
@ -175,6 +178,7 @@
|
||||||
"eslint-plugin-vue": "~8.7.1",
|
"eslint-plugin-vue": "~8.7.1",
|
||||||
"favico.js": "~0.3.10",
|
"favico.js": "~0.3.10",
|
||||||
"get-port-please": "^3.1.1",
|
"get-port-please": "^3.1.1",
|
||||||
|
"jsdom": "^25.0.1",
|
||||||
"node-ssh": "~13.1.0",
|
"node-ssh": "~13.1.0",
|
||||||
"postcss-html": "~1.5.0",
|
"postcss-html": "~1.5.0",
|
||||||
"postcss-rtlcss": "~3.7.2",
|
"postcss-rtlcss": "~3.7.2",
|
||||||
|
@ -193,6 +197,7 @@
|
||||||
"vite": "~5.2.8",
|
"vite": "~5.2.8",
|
||||||
"vite-plugin-compression": "^0.5.1",
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vite-plugin-vue-devtools": "^7.0.15",
|
"vite-plugin-vue-devtools": "^7.0.15",
|
||||||
|
"vitest": "^2.1.5",
|
||||||
"vue": "~3.4.2",
|
"vue": "~3.4.2",
|
||||||
"vue-chartjs": "~5.2.0",
|
"vue-chartjs": "~5.2.0",
|
||||||
"vue-confirm-dialog": "~1.0.2",
|
"vue-confirm-dialog": "~1.0.2",
|
||||||
|
|
100
test/component/MonitorList.spec.js
Normal file
100
test/component/MonitorList.spec.js
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
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", {
|
||||||
|
default: {
|
||||||
|
name: "MonitorListItem",
|
||||||
|
template: "<div class=\"monitor-list-item\"></div>"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("MonitorList.vue", () => {
|
||||||
|
let wrapper;
|
||||||
|
const mockMonitors = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "Test Monitor 1",
|
||||||
|
type: "http",
|
||||||
|
status: "up",
|
||||||
|
url: "https://example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Test Monitor 2",
|
||||||
|
type: "ping",
|
||||||
|
status: "down",
|
||||||
|
hostname: "example.org"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = mount(MonitorList, {
|
||||||
|
props: {
|
||||||
|
monitors: mockMonitors,
|
||||||
|
activeMonitor: null,
|
||||||
|
showTags: true,
|
||||||
|
showStatus: true,
|
||||||
|
showPing: true,
|
||||||
|
showAverage: true
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
stubs: {
|
||||||
|
MonitorListItem: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders monitor list items", () => {
|
||||||
|
const items = wrapper.findAllComponents(MonitorListItem);
|
||||||
|
expect(items).toHaveLength(mockMonitors.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("emits select-monitor event when monitor is clicked", async () => {
|
||||||
|
const items = wrapper.findAll(".monitor-list-item");
|
||||||
|
await items[0].trigger("click");
|
||||||
|
|
||||||
|
expect(wrapper.emitted("select-monitor")).toBeTruthy();
|
||||||
|
expect(wrapper.emitted("select-monitor")[0]).toEqual([mockMonitors[0]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("applies active class to selected monitor", async () => {
|
||||||
|
await wrapper.setProps({
|
||||||
|
activeMonitor: mockMonitors[0]
|
||||||
|
});
|
||||||
|
|
||||||
|
const items = wrapper.findAll(".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);
|
||||||
|
});
|
||||||
|
|
||||||
|
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");
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
114
test/component/PingChart.spec.js
Normal file
114
test/component/PingChart.spec.js
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
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: "<canvas></canvas>"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
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 mockOptions = {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: "Response Time (ms)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = mount(PingChart, {
|
||||||
|
props: {
|
||||||
|
chartData: mockData,
|
||||||
|
options: mockOptions
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
stubs: {
|
||||||
|
Line: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders the chart component", () => {
|
||||||
|
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("passes correct options to chart component", () => {
|
||||||
|
const chart = wrapper.findComponent(Line);
|
||||||
|
expect(chart.props("options")).toEqual(mockOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
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("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("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);
|
||||||
|
});
|
||||||
|
});
|
52
test/component/Status.spec.js
Normal file
52
test/component/Status.spec.js
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { mount } from "@vue/test-utils";
|
||||||
|
import Status from "../../src/components/Status.vue";
|
||||||
|
import { UP, DOWN, PENDING, MAINTENANCE } from "../../src/util";
|
||||||
|
|
||||||
|
describe("Status.vue", () => {
|
||||||
|
const mountStatus = (status) => {
|
||||||
|
return mount(Status, {
|
||||||
|
props: {
|
||||||
|
status
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
it("renders UP status correctly", () => {
|
||||||
|
const wrapper = mountStatus(UP);
|
||||||
|
expect(wrapper.find(".badge").classes()).toContain("bg-success");
|
||||||
|
expect(wrapper.text()).toContain("UP");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders DOWN status correctly", () => {
|
||||||
|
const wrapper = mountStatus(DOWN);
|
||||||
|
expect(wrapper.find(".badge").classes()).toContain("bg-danger");
|
||||||
|
expect(wrapper.text()).toContain("DOWN");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders PENDING status correctly", () => {
|
||||||
|
const wrapper = mountStatus(PENDING);
|
||||||
|
expect(wrapper.find(".badge").classes()).toContain("bg-warning");
|
||||||
|
expect(wrapper.text()).toContain("PENDING");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders MAINTENANCE status correctly", () => {
|
||||||
|
const wrapper = mountStatus(MAINTENANCE);
|
||||||
|
expect(wrapper.find(".badge").classes()).toContain("bg-info");
|
||||||
|
expect(wrapper.text()).toContain("MAINTENANCE");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles unknown status gracefully", () => {
|
||||||
|
const wrapper = mountStatus("UNKNOWN");
|
||||||
|
expect(wrapper.find(".badge").classes()).toContain("bg-secondary");
|
||||||
|
expect(wrapper.text()).toContain("UNKNOWN");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates when status prop changes", async () => {
|
||||||
|
const wrapper = mountStatus(UP);
|
||||||
|
expect(wrapper.find(".badge").classes()).toContain("bg-success");
|
||||||
|
|
||||||
|
await wrapper.setProps({ status: DOWN });
|
||||||
|
expect(wrapper.find(".badge").classes()).toContain("bg-danger");
|
||||||
|
});
|
||||||
|
});
|
22
test/component/setup.js
Normal file
22
test/component/setup.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { config } from "@vue/test-utils";
|
||||||
|
import { vi } from "vitest";
|
||||||
|
|
||||||
|
// Setup global mocks
|
||||||
|
vi.mock("vue-i18n", () => ({
|
||||||
|
useI18n: () => ({
|
||||||
|
t: (key) => key,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Global components mock
|
||||||
|
config.global.stubs = {
|
||||||
|
"font-awesome-icon": true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Global mounting options
|
||||||
|
config.global.mocks = {
|
||||||
|
$t: (key) => key,
|
||||||
|
$filters: {
|
||||||
|
formatDateTime: vi.fn((date) => date.toString()),
|
||||||
|
},
|
||||||
|
};
|
Loading…
Reference in a new issue