feat(tests): add unit tests for BanishedPage and RNPathPage components, enhancing coverage for blocked items and path management
This commit is contained in:
119
tests/frontend/BanishedPage.test.js
Normal file
119
tests/frontend/BanishedPage.test.js
Normal file
@@ -0,0 +1,119 @@
|
||||
import { mount } from "@vue/test-utils";
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import BlockedPage from "@/components/blocked/BlockedPage.vue";
|
||||
import GlobalState from "@/js/GlobalState";
|
||||
|
||||
describe("BlockedPage.vue (Banished UI)", () => {
|
||||
let axiosMock;
|
||||
|
||||
beforeEach(() => {
|
||||
axiosMock = {
|
||||
get: vi.fn(),
|
||||
post: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
};
|
||||
window.axios = axiosMock;
|
||||
|
||||
// Mock localization
|
||||
const t = (key) => {
|
||||
const translations = {
|
||||
"common.save": "Save",
|
||||
"common.cancel": "Cancel",
|
||||
};
|
||||
return translations[key] || key;
|
||||
};
|
||||
|
||||
axiosMock.get.mockImplementation((url) => {
|
||||
if (url === "/api/v1/blocked-destinations") {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
blocked_destinations: [
|
||||
{ destination_hash: "a".repeat(32), created_at: "2026-01-04T12:00:00Z" },
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
if (url === "/api/v1/reticulum/blackhole") {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
blackholed_identities: {
|
||||
["b".repeat(32)]: {
|
||||
source: "c".repeat(32),
|
||||
reason: "Spam",
|
||||
until: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
if (url === "/api/v1/announces") {
|
||||
return Promise.resolve({ data: { announces: [] } });
|
||||
}
|
||||
return Promise.resolve({ data: {} });
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete window.axios;
|
||||
});
|
||||
|
||||
const mountBlockedPage = () => {
|
||||
return mount(BlockedPage, {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (key) => key,
|
||||
},
|
||||
stubs: {
|
||||
MaterialDesignIcon: {
|
||||
template: '<div class="mdi-stub" :data-icon-name="iconName"></div>',
|
||||
props: ["iconName"],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
it("displays 'Banished' title and subtext", async () => {
|
||||
const wrapper = mountBlockedPage();
|
||||
// Wait for isLoading to become false
|
||||
await vi.waitFor(() => expect(wrapper.vm.isLoading).toBe(false));
|
||||
|
||||
expect(wrapper.text()).toContain("Banished");
|
||||
expect(wrapper.text()).toContain("Manage Banished users and nodes");
|
||||
});
|
||||
|
||||
it("combines local blocked and RNS blackholed items", async () => {
|
||||
const wrapper = mountBlockedPage();
|
||||
await vi.waitFor(() => expect(wrapper.vm.isLoading).toBe(false));
|
||||
|
||||
expect(wrapper.vm.allBlockedItems.length).toBe(2);
|
||||
|
||||
const rnsItem = wrapper.vm.allBlockedItems.find((i) => i.is_rns_blackholed);
|
||||
expect(rnsItem).toBeDefined();
|
||||
expect(rnsItem.destination_hash).toBe("b".repeat(32));
|
||||
expect(rnsItem.rns_reason).toBe("Spam");
|
||||
});
|
||||
|
||||
it("displays RNS Blackhole badge for blackholed items", async () => {
|
||||
const wrapper = mountBlockedPage();
|
||||
await vi.waitFor(() => expect(wrapper.vm.isLoading).toBe(false));
|
||||
|
||||
expect(wrapper.text()).toContain("RNS Blackhole");
|
||||
});
|
||||
|
||||
it("calls delete API when lifting banishment", async () => {
|
||||
// Mock DialogUtils.confirm
|
||||
const DialogUtils = await import("@/js/DialogUtils");
|
||||
vi.spyOn(DialogUtils.default, "confirm").mockResolvedValue(true);
|
||||
|
||||
const wrapper = mountBlockedPage();
|
||||
await vi.waitFor(() => expect(wrapper.vm.isLoading).toBe(false));
|
||||
|
||||
const unblockButtons = wrapper.findAll("button").filter((b) => b.text().includes("Lift Banishment"));
|
||||
expect(unblockButtons.length).toBeGreaterThan(0);
|
||||
|
||||
await unblockButtons[0].trigger("click");
|
||||
|
||||
expect(axiosMock.delete).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -14,6 +14,7 @@ describe("LanguageSelector.vue", () => {
|
||||
},
|
||||
stubs: {
|
||||
MaterialDesignIcon: true,
|
||||
Teleport: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -28,20 +29,20 @@ describe("LanguageSelector.vue", () => {
|
||||
const wrapper = mountLanguageSelector();
|
||||
const button = wrapper.find("button");
|
||||
|
||||
expect(wrapper.find(".absolute").exists()).toBe(false);
|
||||
expect(wrapper.find(".fixed").exists()).toBe(false);
|
||||
|
||||
await button.trigger("click");
|
||||
expect(wrapper.find(".absolute").exists()).toBe(true);
|
||||
expect(wrapper.find(".fixed").exists()).toBe(true);
|
||||
|
||||
await button.trigger("click");
|
||||
expect(wrapper.find(".absolute").exists()).toBe(false);
|
||||
expect(wrapper.find(".fixed").exists()).toBe(false);
|
||||
});
|
||||
|
||||
it("lists all available languages in the dropdown", async () => {
|
||||
const wrapper = mountLanguageSelector();
|
||||
await wrapper.find("button").trigger("click");
|
||||
|
||||
const languageButtons = wrapper.findAll(".absolute button");
|
||||
const languageButtons = wrapper.findAll(".fixed button");
|
||||
expect(languageButtons).toHaveLength(3);
|
||||
expect(languageButtons[0].text()).toContain("English");
|
||||
expect(languageButtons[1].text()).toContain("Deutsch");
|
||||
@@ -52,22 +53,22 @@ describe("LanguageSelector.vue", () => {
|
||||
const wrapper = mountLanguageSelector("en");
|
||||
await wrapper.find("button").trigger("click");
|
||||
|
||||
const deButton = wrapper.findAll(".absolute button")[1];
|
||||
const deButton = wrapper.findAll(".fixed button")[1];
|
||||
await deButton.trigger("click");
|
||||
|
||||
expect(wrapper.emitted("language-change")).toBeTruthy();
|
||||
expect(wrapper.emitted("language-change")[0]).toEqual(["de"]);
|
||||
expect(wrapper.find(".absolute").exists()).toBe(false);
|
||||
expect(wrapper.find(".fixed").exists()).toBe(false);
|
||||
});
|
||||
|
||||
it("does not emit language-change when the current language is selected", async () => {
|
||||
const wrapper = mountLanguageSelector("en");
|
||||
await wrapper.find("button").trigger("click");
|
||||
|
||||
const enButton = wrapper.findAll(".absolute button")[0];
|
||||
const enButton = wrapper.findAll(".fixed button")[0];
|
||||
await enButton.trigger("click");
|
||||
|
||||
expect(wrapper.emitted("language-change")).toBeFalsy();
|
||||
expect(wrapper.find(".absolute").exists()).toBe(false);
|
||||
expect(wrapper.find(".fixed").exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -32,6 +32,7 @@ describe("NotificationBell.vue", () => {
|
||||
},
|
||||
stubs: {
|
||||
MaterialDesignIcon: true,
|
||||
Teleport: true,
|
||||
},
|
||||
directives: {
|
||||
"click-outside": {},
|
||||
@@ -172,6 +173,7 @@ describe("NotificationBell.vue", () => {
|
||||
},
|
||||
stubs: {
|
||||
MaterialDesignIcon: true,
|
||||
Teleport: true,
|
||||
},
|
||||
directives: {
|
||||
"click-outside": {},
|
||||
|
||||
105
tests/frontend/RNPathPage.test.js
Normal file
105
tests/frontend/RNPathPage.test.js
Normal file
@@ -0,0 +1,105 @@
|
||||
import { mount } from "@vue/test-utils";
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import RNPathPage from "@/components/tools/RNPathPage.vue";
|
||||
|
||||
describe("RNPathPage.vue", () => {
|
||||
let axiosMock;
|
||||
|
||||
beforeEach(() => {
|
||||
axiosMock = {
|
||||
get: vi.fn(),
|
||||
post: vi.fn(),
|
||||
};
|
||||
window.axios = axiosMock;
|
||||
|
||||
axiosMock.get.mockImplementation((url) => {
|
||||
if (url === "/api/v1/rnpath/table") {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
table: [
|
||||
{
|
||||
hash: "a".repeat(32),
|
||||
hops: 1,
|
||||
via: "b".repeat(32),
|
||||
interface: "UDP",
|
||||
expires: 1234567890,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
if (url === "/api/v1/rnpath/rates") {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
rates: [
|
||||
{
|
||||
hash: "c".repeat(32),
|
||||
last: 1234567890,
|
||||
timestamps: [],
|
||||
rate_violations: 0,
|
||||
blocked_until: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
return Promise.resolve({ data: {} });
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete window.axios;
|
||||
});
|
||||
|
||||
const mountRNPathPage = () => {
|
||||
return mount(RNPathPage, {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (key) => key,
|
||||
},
|
||||
stubs: {
|
||||
MaterialDesignIcon: {
|
||||
template: '<div class="mdi-stub" :data-icon-name="iconName"></div>',
|
||||
props: ["iconName"],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
it("renders and loads data", async () => {
|
||||
const wrapper = mountRNPathPage();
|
||||
await vi.waitFor(() => expect(wrapper.vm.isLoading).toBe(false));
|
||||
|
||||
expect(wrapper.text()).toContain("RNPath");
|
||||
expect(wrapper.vm.pathTable.length).toBe(1);
|
||||
expect(wrapper.vm.rateTable.length).toBe(1);
|
||||
});
|
||||
|
||||
it("switches tabs", async () => {
|
||||
const wrapper = mountRNPathPage();
|
||||
await vi.waitFor(() => expect(wrapper.vm.isLoading).toBe(false));
|
||||
|
||||
const ratesButton = wrapper.findAll("button").find((b) => b.text() === "Rates");
|
||||
await ratesButton.trigger("click");
|
||||
expect(wrapper.vm.tab).toBe("rates");
|
||||
|
||||
const actionsButton = wrapper.findAll("button").find((b) => b.text() === "Actions");
|
||||
await actionsButton.trigger("click");
|
||||
expect(wrapper.vm.tab).toBe("actions");
|
||||
});
|
||||
|
||||
it("calls request path API", async () => {
|
||||
const wrapper = mountRNPathPage();
|
||||
await vi.waitFor(() => expect(wrapper.vm.isLoading).toBe(false));
|
||||
|
||||
await wrapper.setData({ tab: "actions", requestHash: "d".repeat(32) });
|
||||
|
||||
const requestButton = wrapper.findAll("button").find((b) => b.text() === "Request");
|
||||
await requestButton.trigger("click");
|
||||
|
||||
expect(axiosMock.post).toHaveBeenCalledWith("/api/v1/rnpath/request", {
|
||||
destination_hash: "d".repeat(32),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -8,6 +8,14 @@ import ChangelogModal from "../../meshchatx/src/frontend/components/ChangelogMod
|
||||
import NotificationBell from "../../meshchatx/src/frontend/components/NotificationBell.vue";
|
||||
import LanguageSelector from "../../meshchatx/src/frontend/components/LanguageSelector.vue";
|
||||
|
||||
vi.mock("vuetify", () => ({
|
||||
useTheme: vi.fn(() => ({
|
||||
global: {
|
||||
name: { value: "light" },
|
||||
},
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock("../../meshchatx/src/frontend/js/WebSocketConnection", () => ({
|
||||
default: {
|
||||
on: vi.fn(),
|
||||
@@ -344,6 +352,7 @@ describe("Visibility Checks", () => {
|
||||
banished_effect_enabled: true,
|
||||
banished_text: "BANISHED",
|
||||
banished_color: "#dc2626",
|
||||
blackhole_integration_enabled: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -379,6 +388,42 @@ describe("Visibility Checks", () => {
|
||||
delete window.axios;
|
||||
});
|
||||
|
||||
it("SettingsPage shows blackhole integration toggle", async () => {
|
||||
const axiosMock = {
|
||||
get: vi.fn().mockResolvedValue({
|
||||
data: {
|
||||
config: {
|
||||
blackhole_integration_enabled: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
patch: vi.fn().mockResolvedValue({ data: {} }),
|
||||
};
|
||||
window.axios = axiosMock;
|
||||
|
||||
const wrapper = mount(SettingsPage, {
|
||||
global: {
|
||||
stubs: {
|
||||
MaterialDesignIcon: { template: "<div></div>" },
|
||||
Toggle: Toggle,
|
||||
ShortcutRecorder: { template: "<div></div>" },
|
||||
RouterLink: { template: "<a><slot /></a>" },
|
||||
},
|
||||
mocks: {
|
||||
$t: (key) => key,
|
||||
$router: { push: vi.fn() },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.text()).toContain("app.blackhole_integration_enabled");
|
||||
|
||||
delete window.axios;
|
||||
});
|
||||
|
||||
it("SettingsPage hides banished config when toggle is disabled", async () => {
|
||||
const axiosMock = {
|
||||
get: vi.fn().mockResolvedValue({
|
||||
|
||||
Reference in New Issue
Block a user