mirror of
https://git.quad4.io/RNS-Things/MeshChatX.git
synced 2026-04-27 19:25:58 +00:00
ab1be8ea2d
- Added functionality to request paths for preferred propagation nodes during sync. - Introduced new Material Design icons for better visual representation of node states. - Updated settings to allow transfer limits in megabytes, improving user experience. - Enhanced localization support for new features in multiple languages. - Improved tests to cover new propagation node functionalities and UI interactions.
215 lines
8.1 KiB
JavaScript
215 lines
8.1 KiB
JavaScript
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
import PropagationNodesPage from "../../meshchatx/src/frontend/components/propagation-nodes/PropagationNodesPage.vue";
|
|
import ToastUtils from "../../meshchatx/src/frontend/js/ToastUtils";
|
|
|
|
vi.mock("../../meshchatx/src/frontend/js/ToastUtils", () => ({
|
|
default: {
|
|
success: vi.fn(),
|
|
error: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
describe("PropagationNodesPage", () => {
|
|
const axiosMock = {
|
|
get: vi.fn(),
|
|
post: vi.fn(),
|
|
patch: vi.fn(),
|
|
};
|
|
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
vi.clearAllMocks();
|
|
window.api = axiosMock;
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
it("finds local propagation node from list", () => {
|
|
const ctx = {
|
|
propagationNodes: [
|
|
{ destination_hash: "remote-a", is_local_node: false },
|
|
{ destination_hash: "local-node", is_local_node: true },
|
|
],
|
|
};
|
|
const local = PropagationNodesPage.computed.localPropagationNode.call(ctx);
|
|
expect(local.destination_hash).toBe("local-node");
|
|
});
|
|
|
|
it("uses local propagation node as preferred", async () => {
|
|
const ctx = {
|
|
localPropagationNode: { destination_hash: "local-node" },
|
|
usePropagationNode: vi.fn(),
|
|
requestPathForNode: vi.fn(),
|
|
};
|
|
|
|
await PropagationNodesPage.methods.useLocalPropagationNode.call(ctx);
|
|
expect(ctx.usePropagationNode).toHaveBeenCalledWith("local-node");
|
|
expect(ctx.requestPathForNode).toHaveBeenCalledWith("local-node");
|
|
});
|
|
|
|
it("prefers runtime local node state for running indicator", () => {
|
|
const runningByStats = PropagationNodesPage.computed.localNodeIsRunning.call({
|
|
localPropagationNode: {
|
|
is_propagation_enabled: true,
|
|
local_node_stats: { is_running: false },
|
|
},
|
|
});
|
|
expect(runningByStats).toBe(false);
|
|
});
|
|
|
|
it("formats storage usage with limit when available", () => {
|
|
const ctx = {
|
|
formatByteSize: PropagationNodesPage.methods.formatByteSize,
|
|
};
|
|
const text = PropagationNodesPage.methods.formatStorageUsage.call(ctx, {
|
|
messagestore_bytes: 76500,
|
|
messagestore_limit_bytes: 10240000,
|
|
});
|
|
expect(text).toBe("76.5 KB / 10.24 MB");
|
|
});
|
|
|
|
it("debounces propagation transfer limit save", async () => {
|
|
const ctx = {
|
|
propagationLimitInputMb: 1.234,
|
|
saveTimeouts: {
|
|
propagationLimit: null,
|
|
},
|
|
mbToBytes: PropagationNodesPage.methods.mbToBytes,
|
|
updateConfig: vi.fn().mockResolvedValue(undefined),
|
|
};
|
|
|
|
await PropagationNodesPage.methods.onPropagationTransferLimitChange.call(ctx);
|
|
expect(ctx.updateConfig).not.toHaveBeenCalled();
|
|
|
|
await vi.advanceTimersByTimeAsync(500);
|
|
expect(ctx.updateConfig).toHaveBeenCalledWith({
|
|
lxmf_propagation_transfer_limit_in_bytes: 1234000,
|
|
});
|
|
});
|
|
|
|
it("debounces propagation stamp cost save with bounds", async () => {
|
|
const ctx = {
|
|
config: {
|
|
lxmf_propagation_node_stamp_cost: 3,
|
|
},
|
|
saveTimeouts: {
|
|
propagationStampCost: null,
|
|
},
|
|
updateConfig: vi.fn().mockResolvedValue(undefined),
|
|
};
|
|
await PropagationNodesPage.methods.onPropagationStampCostChange.call(ctx);
|
|
await vi.advanceTimersByTimeAsync(500);
|
|
expect(ctx.updateConfig).toHaveBeenCalledWith({
|
|
lxmf_propagation_node_stamp_cost: 13,
|
|
});
|
|
});
|
|
|
|
it("stops and restarts local node via API", async () => {
|
|
axiosMock.post.mockResolvedValue({ data: {} });
|
|
const ctx = {
|
|
getConfig: vi.fn().mockResolvedValue(undefined),
|
|
loadPropagationNodes: vi.fn().mockResolvedValue(undefined),
|
|
refreshPriorityNodePaths: vi.fn().mockResolvedValue(undefined),
|
|
$t: (k) => k,
|
|
};
|
|
|
|
await PropagationNodesPage.methods.stopLocalPropagationNode.call(ctx);
|
|
await PropagationNodesPage.methods.restartLocalPropagationNode.call(ctx);
|
|
|
|
expect(axiosMock.post).toHaveBeenCalledWith("/api/v1/lxmf/propagation-node/stop");
|
|
expect(axiosMock.post).toHaveBeenCalledWith("/api/v1/lxmf/propagation-node/restart");
|
|
expect(ToastUtils.success).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it("triggers announce via icon action", async () => {
|
|
axiosMock.get.mockResolvedValue({ data: {} });
|
|
const ctx = {
|
|
loadPropagationNodes: vi.fn().mockResolvedValue(undefined),
|
|
refreshPriorityNodePaths: vi.fn().mockResolvedValue(undefined),
|
|
$t: (k) => k,
|
|
};
|
|
await PropagationNodesPage.methods.announceNow.call(ctx);
|
|
expect(axiosMock.get).toHaveBeenCalledWith("/api/v1/announce");
|
|
expect(ToastUtils.success).toHaveBeenCalledWith("Announce triggered");
|
|
});
|
|
|
|
it("resets local node display name to Anonymous Peer", async () => {
|
|
const ctx = {
|
|
localNodeDisplayNameDraft: "Custom Name",
|
|
saveLocalNodeDisplayName: vi.fn().mockResolvedValue(undefined),
|
|
};
|
|
await PropagationNodesPage.methods.resetLocalNodeDisplayName.call(ctx);
|
|
expect(ctx.localNodeDisplayNameDraft).toBe("Anonymous Peer");
|
|
expect(ctx.saveLocalNodeDisplayName).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("uses collapsed manager on small screens", () => {
|
|
const originalMatchMedia = window.matchMedia;
|
|
window.matchMedia = vi.fn().mockReturnValue({ matches: true });
|
|
const ctx = {
|
|
isLocalManagerCollapsed: false,
|
|
getConfig: vi.fn(),
|
|
loadPropagationNodes: vi.fn(),
|
|
refreshPriorityNodePaths: vi.fn(),
|
|
};
|
|
PropagationNodesPage.mounted.call(ctx);
|
|
expect(ctx.isLocalManagerCollapsed).toBe(true);
|
|
window.matchMedia = originalMatchMedia;
|
|
});
|
|
|
|
it("saves local display name and announces immediately", async () => {
|
|
axiosMock.patch.mockResolvedValue({
|
|
data: {
|
|
config: {
|
|
display_name: "Friendly Node",
|
|
lxmf_delivery_transfer_limit_in_bytes: 10000000,
|
|
lxmf_propagation_transfer_limit_in_bytes: 256000,
|
|
lxmf_propagation_sync_limit_in_bytes: 10240000,
|
|
},
|
|
},
|
|
});
|
|
axiosMock.get.mockResolvedValue({ data: {} });
|
|
|
|
const ctx = {
|
|
localNodeDisplayNameDraft: " Friendly Node ",
|
|
config: {
|
|
lxmf_delivery_transfer_limit_in_bytes: 10000000,
|
|
lxmf_propagation_transfer_limit_in_bytes: 256000,
|
|
lxmf_propagation_sync_limit_in_bytes: 10240000,
|
|
},
|
|
syncManagerInputsFromConfig: vi.fn(),
|
|
loadPropagationNodes: vi.fn().mockResolvedValue(undefined),
|
|
refreshPriorityNodePaths: vi.fn().mockResolvedValue(undefined),
|
|
announceNow: PropagationNodesPage.methods.announceNow,
|
|
updateConfig: PropagationNodesPage.methods.updateConfig,
|
|
$t: (k) => k,
|
|
};
|
|
|
|
await PropagationNodesPage.methods.saveLocalNodeDisplayName.call(ctx);
|
|
|
|
expect(axiosMock.patch).toHaveBeenCalledWith("/api/v1/config", {
|
|
display_name: "Friendly Node",
|
|
});
|
|
expect(axiosMock.get).toHaveBeenCalledWith("/api/v1/announce");
|
|
expect(ToastUtils.success).toHaveBeenCalledWith("Name saved and announced");
|
|
});
|
|
|
|
it("fetches path for a destination hash", async () => {
|
|
axiosMock.get.mockResolvedValueOnce({
|
|
data: {
|
|
path: { hops: 2, next_hop_interface: "TCP Client" },
|
|
},
|
|
});
|
|
const ctx = {
|
|
nodePathsByHash: {},
|
|
};
|
|
await PropagationNodesPage.methods.requestPathForNode.call(ctx, "abcd");
|
|
expect(axiosMock.get).toHaveBeenCalledWith("/api/v1/destination/abcd/path", {
|
|
params: { request: "1", timeout: 4 },
|
|
});
|
|
expect(ctx.nodePathsByHash.abcd).toEqual({ hops: 2, next_hop_interface: "TCP Client" });
|
|
});
|
|
});
|