Fix NodeInfoDialog initiation (#626)
This commit is contained in:
@@ -1,18 +1,14 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { render, screen } from "@testing-library/react";
|
import { render, screen } from "@testing-library/react";
|
||||||
import { NodeDetailsDialog } from "@components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx";
|
import { NodeDetailsDialog } from "@components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx";
|
||||||
|
import { useDevice } from "@core/stores/deviceStore.ts";
|
||||||
import { useAppStore } from "@core/stores/appStore.ts";
|
import { useAppStore } from "@core/stores/appStore.ts";
|
||||||
import { Protobuf } from "@meshtastic/core";
|
import { Protobuf } from "@meshtastic/core";
|
||||||
|
|
||||||
vi.mock("@core/stores/deviceStore", () => {
|
vi.mock("@core/stores/deviceStore");
|
||||||
return {
|
|
||||||
useDevice: () => ({
|
|
||||||
setDialogOpen: vi.fn(),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
vi.mock("@core/stores/appStore");
|
vi.mock("@core/stores/appStore");
|
||||||
|
|
||||||
|
const mockUseDevice = vi.mocked(useDevice);
|
||||||
const mockUseAppStore = vi.mocked(useAppStore);
|
const mockUseAppStore = vi.mocked(useAppStore);
|
||||||
|
|
||||||
describe("NodeDetailsDialog", () => {
|
describe("NodeDetailsDialog", () => {
|
||||||
@@ -42,13 +38,22 @@ describe("NodeDetailsDialog", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.resetAllMocks();
|
vi.resetAllMocks();
|
||||||
|
|
||||||
|
mockUseDevice.mockReturnValue({
|
||||||
|
getNode: (nodeNum: number) => {
|
||||||
|
if (nodeNum === 1234) {
|
||||||
|
return mockNode;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
mockUseAppStore.mockReturnValue({
|
mockUseAppStore.mockReturnValue({
|
||||||
nodeNumDetails: 1234,
|
nodeNumDetails: 1234,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders node details correctly", () => {
|
it("renders node details correctly", () => {
|
||||||
render(<NodeDetailsDialog open node={mockNode} onOpenChange={() => {}} />);
|
render(<NodeDetailsDialog open onOpenChange={() => {}} />);
|
||||||
|
|
||||||
expect(screen.getByText(/Node Details for Test Node \(TN\)/i))
|
expect(screen.getByText(/Node Details for Test Node \(TN\)/i))
|
||||||
.toBeInTheDocument();
|
.toBeInTheDocument();
|
||||||
@@ -78,10 +83,26 @@ describe("NodeDetailsDialog", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("renders null if node is undefined", () => {
|
it("renders null if node is undefined", () => {
|
||||||
const mockNode = undefined;
|
const requestedNodeNum = 5678;
|
||||||
|
|
||||||
|
mockUseAppStore.mockReturnValue({
|
||||||
|
nodeNumDetails: requestedNodeNum,
|
||||||
|
});
|
||||||
|
|
||||||
|
mockUseDevice.mockReturnValue({
|
||||||
|
getNode: (nodeNum: number) => {
|
||||||
|
if (nodeNum === requestedNodeNum) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (nodeNum === 1234) {
|
||||||
|
return mockNode;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<NodeDetailsDialog open node={mockNode} onOpenChange={() => {}} />,
|
<NodeDetailsDialog open onOpenChange={() => {}} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(container.firstChild).toBeNull();
|
expect(container.firstChild).toBeNull();
|
||||||
@@ -90,14 +111,10 @@ describe("NodeDetailsDialog", () => {
|
|||||||
|
|
||||||
it("renders correctly when position is missing", () => {
|
it("renders correctly when position is missing", () => {
|
||||||
const nodeWithoutPosition = { ...mockNode, position: undefined };
|
const nodeWithoutPosition = { ...mockNode, position: undefined };
|
||||||
|
mockUseDevice.mockReturnValue({ getNode: () => nodeWithoutPosition });
|
||||||
|
mockUseAppStore.mockReturnValue({ nodeNumDetails: 1234 });
|
||||||
|
|
||||||
render(
|
render(<NodeDetailsDialog open onOpenChange={() => {}} />);
|
||||||
<NodeDetailsDialog
|
|
||||||
open
|
|
||||||
node={nodeWithoutPosition}
|
|
||||||
onOpenChange={() => {}}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(screen.queryByText(/Coordinates:/i)).not.toBeInTheDocument();
|
expect(screen.queryByText(/Coordinates:/i)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText(/Altitude:/i)).not.toBeInTheDocument();
|
expect(screen.queryByText(/Altitude:/i)).not.toBeInTheDocument();
|
||||||
@@ -106,14 +123,10 @@ describe("NodeDetailsDialog", () => {
|
|||||||
|
|
||||||
it("renders correctly when deviceMetrics are missing", () => {
|
it("renders correctly when deviceMetrics are missing", () => {
|
||||||
const nodeWithoutMetrics = { ...mockNode, deviceMetrics: undefined };
|
const nodeWithoutMetrics = { ...mockNode, deviceMetrics: undefined };
|
||||||
|
mockUseDevice.mockReturnValue({ getNode: () => nodeWithoutMetrics });
|
||||||
|
mockUseAppStore.mockReturnValue({ nodeNumDetails: 1234 });
|
||||||
|
|
||||||
render(
|
render(<NodeDetailsDialog open onOpenChange={() => {}} />);
|
||||||
<NodeDetailsDialog
|
|
||||||
open
|
|
||||||
node={nodeWithoutMetrics}
|
|
||||||
onOpenChange={() => {}}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(screen.queryByText(/Device Metrics:/i)).not.toBeInTheDocument();
|
expect(screen.queryByText(/Device Metrics:/i)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText(/Air TX utilization:/i)).not.toBeInTheDocument();
|
expect(screen.queryByText(/Air TX utilization:/i)).not.toBeInTheDocument();
|
||||||
@@ -122,10 +135,10 @@ describe("NodeDetailsDialog", () => {
|
|||||||
|
|
||||||
it("renders 'Never' for lastHeard when timestamp is 0", () => {
|
it("renders 'Never' for lastHeard when timestamp is 0", () => {
|
||||||
const nodeNeverHeard = { ...mockNode, lastHeard: 0 };
|
const nodeNeverHeard = { ...mockNode, lastHeard: 0 };
|
||||||
|
mockUseDevice.mockReturnValue({ getNode: () => nodeNeverHeard });
|
||||||
|
mockUseAppStore.mockReturnValue({ nodeNumDetails: 1234 });
|
||||||
|
|
||||||
render(
|
render(<NodeDetailsDialog open onOpenChange={() => {}} />);
|
||||||
<NodeDetailsDialog open node={nodeNeverHeard} onOpenChange={() => {}} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(screen.getByText(/Last Heard: Never/i)).toBeInTheDocument();
|
expect(screen.getByText(/Last Heard: Never/i)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -50,25 +50,28 @@ import {
|
|||||||
import { Separator } from "@components/UI/Seperator.tsx";
|
import { Separator } from "@components/UI/Seperator.tsx";
|
||||||
|
|
||||||
export interface NodeDetailsDialogProps {
|
export interface NodeDetailsDialogProps {
|
||||||
node: Protobuf.Mesh.NodeInfo | undefined;
|
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NodeDetailsDialog = ({
|
export const NodeDetailsDialog = ({
|
||||||
node,
|
|
||||||
open,
|
open,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
}: NodeDetailsDialogProps) => {
|
}: NodeDetailsDialogProps) => {
|
||||||
const { setDialogOpen, connection, setActivePage } = useDevice();
|
const { setDialogOpen, connection, setActivePage, getNode } = useDevice();
|
||||||
const { setNodeNumToBeRemoved } = useAppStore();
|
const { setNodeNumToBeRemoved, nodeNumDetails } = useAppStore();
|
||||||
const { setChatType, setActiveChat } = useMessageStore();
|
const { setChatType, setActiveChat } = useMessageStore();
|
||||||
|
|
||||||
const { updateFavorite } = useFavoriteNode();
|
const { updateFavorite } = useFavoriteNode();
|
||||||
const [isFavoriteState, setIsFavoriteState] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const { updateIgnored } = useIgnoreNode();
|
const { updateIgnored } = useIgnoreNode();
|
||||||
const [isIgnoredState, setIsIgnoredState] = useState<boolean>(false);
|
|
||||||
|
const node = getNode(nodeNumDetails);
|
||||||
|
|
||||||
|
const [isFavoriteState, setIsFavoriteState] = useState<boolean>(
|
||||||
|
node?.isFavorite ?? false,
|
||||||
|
);
|
||||||
|
const [isIgnoredState, setIsIgnoredState] = useState<boolean>(
|
||||||
|
node?.isIgnored ?? false,
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!node) return;
|
if (!node) return;
|
||||||
|
|||||||
+12
-12
@@ -1,5 +1,4 @@
|
|||||||
import { LocationResponseDialog } from "@app/components/Dialog/LocationResponseDialog.tsx";
|
import { LocationResponseDialog } from "@app/components/Dialog/LocationResponseDialog.tsx";
|
||||||
import { NodeDetailsDialog } from "@app/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx";
|
|
||||||
import { TracerouteResponseDialog } from "@app/components/Dialog/TracerouteResponseDialog.tsx";
|
import { TracerouteResponseDialog } from "@app/components/Dialog/TracerouteResponseDialog.tsx";
|
||||||
import { Sidebar } from "@components/Sidebar.tsx";
|
import { Sidebar } from "@components/Sidebar.tsx";
|
||||||
import { Avatar } from "@components/UI/Avatar.tsx";
|
import { Avatar } from "@components/UI/Avatar.tsx";
|
||||||
@@ -7,6 +6,7 @@ import { Mono } from "@components/generic/Mono.tsx";
|
|||||||
import { Table } from "@components/generic/Table/index.tsx";
|
import { Table } from "@components/generic/Table/index.tsx";
|
||||||
import { TimeAgo } from "@components/generic/TimeAgo.tsx";
|
import { TimeAgo } from "@components/generic/TimeAgo.tsx";
|
||||||
import { useDevice } from "@core/stores/deviceStore.ts";
|
import { useDevice } from "@core/stores/deviceStore.ts";
|
||||||
|
import { useAppStore } from "@core/stores/appStore.ts";
|
||||||
import { Protobuf, type Types } from "@meshtastic/core";
|
import { Protobuf, type Types } from "@meshtastic/core";
|
||||||
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
|
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
|
||||||
import { LockIcon, LockOpenIcon } from "lucide-react";
|
import { LockIcon, LockOpenIcon } from "lucide-react";
|
||||||
@@ -33,11 +33,11 @@ export interface DeleteNoteDialogProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const NodesPage = (): JSX.Element => {
|
const NodesPage = (): JSX.Element => {
|
||||||
const { getNodes, hardware, connection, hasNodeError } = useDevice();
|
const { getNodes, hardware, connection, hasNodeError, setDialogOpen } =
|
||||||
|
useDevice();
|
||||||
|
const { setNodeNumDetails } = useAppStore();
|
||||||
const { nodeFilter, defaultFilterValues, isFilterDirty } = useFilterNode();
|
const { nodeFilter, defaultFilterValues, isFilterDirty } = useFilterNode();
|
||||||
const [selectedNode, setSelectedNode] = useState<
|
|
||||||
Protobuf.Mesh.NodeInfo | undefined
|
|
||||||
>(undefined);
|
|
||||||
const [selectedTraceroute, setSelectedTraceroute] = useState<
|
const [selectedTraceroute, setSelectedTraceroute] = useState<
|
||||||
Types.PacketMetadata<Protobuf.Mesh.RouteDiscovery> | undefined
|
Types.PacketMetadata<Protobuf.Mesh.RouteDiscovery> | undefined
|
||||||
>();
|
>();
|
||||||
@@ -86,6 +86,11 @@ const NodesPage = (): JSX.Element => {
|
|||||||
[hardware.myNodeNum],
|
[hardware.myNodeNum],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function handleNodeInfoDialog(nodeNum: number): void {
|
||||||
|
setNodeNumDetails(nodeNum);
|
||||||
|
setDialogOpen("nodeDetails", true);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageLayout
|
<PageLayout
|
||||||
@@ -146,9 +151,9 @@ const NodesPage = (): JSX.Element => {
|
|||||||
</div>,
|
</div>,
|
||||||
<h1
|
<h1
|
||||||
key="longName"
|
key="longName"
|
||||||
onMouseDown={() => setSelectedNode(node)}
|
onMouseDown={() => handleNodeInfoDialog(node.num)}
|
||||||
onKeyUp={(evt) => {
|
onKeyUp={(evt) => {
|
||||||
evt.key === "Enter" && setSelectedNode(node);
|
evt.key === "Enter" && handleNodeInfoDialog(node.num);
|
||||||
}}
|
}}
|
||||||
className="cursor-pointer underline ml-2 whitespace-break-spaces"
|
className="cursor-pointer underline ml-2 whitespace-break-spaces"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
@@ -192,11 +197,6 @@ const NodesPage = (): JSX.Element => {
|
|||||||
</Mono>,
|
</Mono>,
|
||||||
])}
|
])}
|
||||||
/>
|
/>
|
||||||
<NodeDetailsDialog
|
|
||||||
node={selectedNode}
|
|
||||||
open={!!selectedNode}
|
|
||||||
onOpenChange={() => setSelectedNode(undefined)}
|
|
||||||
/>
|
|
||||||
<TracerouteResponseDialog
|
<TracerouteResponseDialog
|
||||||
traceroute={selectedTraceroute}
|
traceroute={selectedTraceroute}
|
||||||
open={!!selectedTraceroute}
|
open={!!selectedTraceroute}
|
||||||
|
|||||||
Reference in New Issue
Block a user