feat(tests): add comprehensive test suite for backend functionality, including database, configuration, and telemetry utilities
All checks were successful
CI / test-backend (push) Successful in 15s
CI / lint (push) Successful in 42s
CI / build-frontend (push) Successful in 9m35s

This commit is contained in:
2026-01-02 19:41:05 -06:00
parent d7a5926e6e
commit 00b4290735
15 changed files with 895 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
import { mount } from "@vue/test-utils";
import { describe, it, expect } from "vitest";
import IconButton from "../../meshchatx/src/frontend/components/IconButton.vue";
describe("IconButton.vue", () => {
it("renders slot content", () => {
const wrapper = mount(IconButton, {
slots: {
default: '<span class="test-icon">icon</span>',
},
});
expect(wrapper.find(".test-icon").exists()).toBe(true);
expect(wrapper.text()).toBe("icon");
});
it("has correct button type", () => {
const wrapper = mount(IconButton);
expect(wrapper.attributes("type")).toBe("button");
});
});

View File

@@ -0,0 +1,110 @@
import { mount } from "@vue/test-utils";
import { describe, it, expect, vi, beforeEach } from "vitest";
import InterfacesPage from "../../meshchatx/src/frontend/components/interfaces/InterfacesPage.vue";
// Mock global objects
const mockAxios = {
get: vi.fn(),
post: vi.fn(),
};
window.axios = mockAxios;
const mockToast = {
success: vi.fn(),
error: vi.fn(),
};
// We need to handle how ToastUtils is imported in the component
// If it's a global or imported, we might need a different approach.
// Let's assume it's available via window or we can mock the import if using vitest aliases.
vi.mock("../../js/ToastUtils", () => ({
default: {
success: vi.fn(),
error: vi.fn(),
},
}));
// Mock router/route
const mockRoute = {
query: {},
};
const mockRouter = {
push: vi.fn(),
};
describe("InterfacesPage.vue", () => {
beforeEach(() => {
vi.clearAllMocks();
mockAxios.get.mockResolvedValue({ data: { interfaces: [], app_info: { is_reticulum_running: true } } });
});
it("loads interfaces on mount", async () => {
mockAxios.get.mockImplementation((url) => {
if (url.includes("interfaces")) {
return Promise.resolve({ data: { interfaces: [{ name: "Test Iface", type: "TCP" }] } });
}
if (url.includes("app/info")) {
return Promise.resolve({ data: { app_info: { is_reticulum_running: true } } });
}
return Promise.reject();
});
const wrapper = mount(InterfacesPage, {
global: {
mocks: {
$route: mockRoute,
$router: mockRouter,
$t: (msg) => msg,
},
stubs: ["MaterialDesignIcon", "IconButton", "Interface", "ImportInterfacesModal"],
},
});
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick(); // wait for multiple awaits
expect(mockAxios.get).toHaveBeenCalledWith("/api/v1/reticulum/interfaces");
expect(wrapper.vm.interfaces.length).toBe(1);
});
it("tracks changes when an interface is enabled", async () => {
const wrapper = mount(InterfacesPage, {
global: {
mocks: {
$route: mockRoute,
$router: mockRouter,
$t: (msg) => msg,
},
stubs: ["MaterialDesignIcon", "IconButton", "Interface", "ImportInterfacesModal"],
},
});
await wrapper.vm.enableInterface("test-iface");
expect(wrapper.vm.hasPendingInterfaceChanges).toBe(true);
expect(wrapper.vm.modifiedInterfaceNames.has("test-iface")).toBe(true);
});
it("clears pending changes after RNS reload", async () => {
mockAxios.post.mockResolvedValue({ data: { message: "Reloaded" } });
const wrapper = mount(InterfacesPage, {
global: {
mocks: {
$route: mockRoute,
$router: mockRouter,
$t: (msg) => msg,
},
stubs: ["MaterialDesignIcon", "IconButton", "Interface", "ImportInterfacesModal"],
},
});
wrapper.vm.hasPendingInterfaceChanges = true;
wrapper.vm.modifiedInterfaceNames.add("test-iface");
await wrapper.vm.reloadRns();
expect(wrapper.vm.hasPendingInterfaceChanges).toBe(false);
expect(wrapper.vm.modifiedInterfaceNames.size).toBe(0);
expect(mockAxios.post).toHaveBeenCalledWith("/api/v1/reticulum/reload");
});
});

View File

@@ -0,0 +1,27 @@
import { mount } from "@vue/test-utils";
import { describe, it, expect } from "vitest";
import MaterialDesignIcon from "../../meshchatx/src/frontend/components/MaterialDesignIcon.vue";
describe("MaterialDesignIcon.vue", () => {
it("converts icon-name to mdiIconName", () => {
const wrapper = mount(MaterialDesignIcon, {
props: { iconName: "account-circle" },
});
expect(wrapper.vm.mdiIconName).toBe("mdiAccountCircle");
});
it("renders svg with correct aria-label", () => {
const wrapper = mount(MaterialDesignIcon, {
props: { iconName: "home" },
});
expect(wrapper.find("svg").attributes("aria-label")).toBe("home");
});
it("falls back to question mark for unknown icons", () => {
const wrapper = mount(MaterialDesignIcon, {
props: { iconName: "non-existent-icon" },
});
// mdiProgressQuestion should be used
expect(wrapper.vm.iconPath).not.toBe("");
});
});

View File

@@ -0,0 +1,28 @@
import { mount } from "@vue/test-utils";
import { describe, it, expect } from "vitest";
import Toggle from "../../meshchatx/src/frontend/components/forms/Toggle.vue";
describe("Toggle.vue", () => {
it("renders label when provided", () => {
const wrapper = mount(Toggle, {
props: { id: "test-toggle", label: "Test Label" },
});
expect(wrapper.text()).toContain("Test Label");
});
it("emits update:modelValue on change", async () => {
const wrapper = mount(Toggle, {
props: { id: "test-toggle", modelValue: false },
});
const input = wrapper.find("input");
await input.setChecked(true);
expect(wrapper.emitted("update:modelValue")[0]).toEqual([true]);
});
it("reflects modelValue prop", () => {
const wrapper = mount(Toggle, {
props: { id: "test-toggle", modelValue: true },
});
expect(wrapper.find("input").element.checked).toBe(true);
});
});