Browse Source

Fix NodeInfoDialog initiation (#626)

pull/632/head
philon- 1 year ago
committed by GitHub
parent
commit
37a53b747c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 67
      src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.test.tsx
  2. 19
      src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx
  3. 24
      src/pages/Nodes.tsx

67
src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.test.tsx

@ -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();
}); });

19
src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx

@ -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;

24
src/pages/Nodes.tsx

@ -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}

Loading…
Cancel
Save