Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 77 additions & 30 deletions src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.test.tsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,102 @@
import { render, screen, fireEvent } from "@testing-library/react";
import { beforeEach, describe, expect, it, Mock, vi } from "vitest";
import { RefreshKeysDialog } from "./RefreshKeysDialog";
import { beforeEach, describe, expect, it, vi, Mock } from "vitest";
import { RefreshKeysDialog } from "./RefreshKeysDialog.tsx";
import { useRefreshKeysDialog } from "./useRefreshKeysDialog.ts";
import { useMessageStore } from "@core/stores/messageStore.ts"; // Import for mocking
import { useDevice } from "@core/stores/deviceStore.ts"; // Import for mocking
import { Protobuf } from "@meshtastic/core";


vi.mock("@core/stores/messageStore.ts", () => ({
useMessageStore: vi.fn(),
}));

const mockNodeWithError: Partial<Protobuf.Mesh.NodeInfo> = {
user: { longName: "Test Node Long", shortName: "TNL", id: 456 },
};
const mockNodes = new Map([[456, mockNodeWithError]]);
const mockNodeErrors = new Map([[123, { node: 456 }]]);

vi.mock("@core/stores/deviceStore.ts", () => ({
useDevice: vi.fn(),
}));

const mockHandleCloseDialog = vi.fn();
const mockHandleNodeRemove = vi.fn();
vi.mock("./useRefreshKeysDialog.ts", () => ({
useRefreshKeysDialog: vi.fn(),
useRefreshKeysDialog: vi.fn(() => ({
handleCloseDialog: mockHandleCloseDialog,
handleNodeRemove: mockHandleNodeRemove,
})),
}));

describe("RefreshKeysDialog Component", () => {
let handleCloseDialogMock: Mock;
let handleNodeRemoveMock: Mock;
let onOpenChangeMock: Mock;

beforeEach(() => {
handleCloseDialogMock = vi.fn();
handleNodeRemoveMock = vi.fn();
vi.clearAllMocks();
onOpenChangeMock = vi.fn();

(useRefreshKeysDialog as Mock).mockReturnValue({
handleCloseDialog: handleCloseDialogMock,
handleNodeRemove: handleNodeRemoveMock,
vi.mocked(useMessageStore).mockReturnValue({ activeChat: 123 });
vi.mocked(useDevice).mockReturnValue({
nodeErrors: mockNodeErrors,
nodes: mockNodes,
});
vi.mocked(useRefreshKeysDialog).mockReturnValue({
handleCloseDialog: mockHandleCloseDialog,
handleNodeRemove: mockHandleNodeRemove,
});
});

it("renders the dialog with correct content", () => {
render(<RefreshKeysDialog open={true} onOpenChange={onOpenChangeMock} />);
expect(screen.getByText("Keys Mismatch")).toBeInTheDocument();
expect(screen.getByText("Request New Keys")).toBeInTheDocument();
expect(screen.getByText("Dismiss")).toBeInTheDocument();
it("should render the dialog with dynamic content when open and data is available", () => {
render(<RefreshKeysDialog open onOpenChange={onOpenChangeMock} />);

expect(screen.getByText(`Keys Mismatch - ${mockNodeWithError?.user?.longName}`)).toBeInTheDocument();
expect(screen.getByText(new RegExp(`${mockNodeWithError?.user?.longName}.*${mockNodeWithError?.user?.shortName}`))).toBeInTheDocument();
expect(screen.getByRole('button', { name: /request new keys/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /dismiss/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /Close/i })).toBeInTheDocument();
});

it("calls handleNodeRemove when 'Request New Keys' button is clicked", () => {
render(<RefreshKeysDialog open={true} onOpenChange={onOpenChangeMock} />);
fireEvent.click(screen.getByText("Request New Keys"));
expect(handleNodeRemoveMock).toHaveBeenCalled();
it("should call handleNodeRemove when 'Request New Keys' button is clicked", () => {
render(<RefreshKeysDialog open onOpenChange={onOpenChangeMock} />);
fireEvent.click(screen.getByRole('button', { name: /request new keys/i }));
expect(mockHandleNodeRemove).toHaveBeenCalledTimes(1);
});

it("calls handleCloseDialog when 'Dismiss' button is clicked", () => {
render(<RefreshKeysDialog open={true} onOpenChange={onOpenChangeMock} />);
fireEvent.click(screen.getByText("Dismiss"));
expect(handleCloseDialogMock).toHaveBeenCalled();
it("should call handleCloseDialog when 'Dismiss' button is clicked", () => {
render(<RefreshKeysDialog open onOpenChange={onOpenChangeMock} />);
fireEvent.click(screen.getByRole('button', { name: /dismiss/i }));
expect(mockHandleCloseDialog).toHaveBeenCalledTimes(1);
});

it("calls onOpenChange when dialog close button is clicked", () => {
render(<RefreshKeysDialog open={true} onOpenChange={onOpenChangeMock} />);
fireEvent.click(screen.getByRole("button", { name: /close/i }));
expect(handleCloseDialogMock).toHaveBeenCalled();
it("should call handleCloseDialog when the explicit DialogClose button is clicked", () => {
render(<RefreshKeysDialog open onOpenChange={onOpenChangeMock} />);
fireEvent.click(screen.getByRole('button', { name: /close/i })); // Use the aria-label
expect(mockHandleCloseDialog).toHaveBeenCalledTimes(1);
});

it("does not render when open is false", () => {

it("should not render the dialog when open is false", () => {
render(<RefreshKeysDialog open={false} onOpenChange={onOpenChangeMock} />);
expect(screen.queryByText("Keys Mismatch")).not.toBeInTheDocument();
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
});

it("should render null if nodeErrorNum is not found for activeChat", () => {
vi.mocked(useDevice).mockReturnValue({
nodeErrors: new Map(),
nodes: mockNodes,
});
const { container } = render(<RefreshKeysDialog open onOpenChange={onOpenChangeMock} />);
expect(container.firstChild).toBeNull();
});

it("should render null if nodeWithError is not found for nodeErrorNum.node", () => {
vi.mocked(useDevice).mockReturnValue({
nodeErrors: mockNodeErrors,
nodes: new Map(),
});
const { container } = render(<RefreshKeysDialog open onOpenChange={onOpenChangeMock} />);
expect(container.firstChild).toBeNull();
});
});
});
30 changes: 25 additions & 5 deletions src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,45 @@ import {
import { Button } from "@components/UI/Button.tsx";
import { LockKeyholeOpenIcon } from "lucide-react";
import { useRefreshKeysDialog } from "./useRefreshKeysDialog.ts";
import { useDevice } from "@core/stores/deviceStore.ts";
import { useMessageStore } from "@core/stores/messageStore.ts";

export interface RefreshKeysDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
}

export const RefreshKeysDialog = ({ open, onOpenChange }: RefreshKeysDialogProps) => {

const { activeChat } = useMessageStore();
const { nodeErrors, nodes } = useDevice();
const { handleCloseDialog, handleNodeRemove } = useRefreshKeysDialog();

const nodeErrorNum = nodeErrors.get(activeChat);

if (!nodeErrorNum) {
console.error("Node with error not found");
return null;
}

const nodeWithError = nodes.get(nodeErrorNum?.node ?? 0);

if (!nodeWithError) {
console.error("Node with error not found");
return null;
}

const text = {
title: `Keys Mismatch - ${nodeWithError?.user?.longName ?? ""}`,
description: `Your node is unable to send a direct message to node: ${nodeWithError?.user?.longName ?? ""} (${nodeWithError?.user?.shortName ?? ""}). This is due to the remote node's current public key does not match the previously stored key for this node.`,
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-8 flex flex-col gap-2">
<DialogClose onClick={handleCloseDialog} />
<DialogHeader>
<DialogTitle>Keys Mismatch</DialogTitle>
<DialogTitle>{text.title}</DialogTitle>
</DialogHeader>
Your node is unable to send a direct message to this node. This is due to the remote node's current public key not matching the previously stored key for this node.
{text.description}
<ul className="mt-2">
<li className="flex place-items-center gap-2 items-start">
<div className="p-2 bg-slate-500 rounded-lg mt-1">
Expand All @@ -40,14 +62,12 @@ export const RefreshKeysDialog = ({ open, onOpenChange }: RefreshKeysDialogProps
<Button
variant="default"
onClick={handleNodeRemove}
className=""
>
Request New Keys
</Button>
<Button
variant="outline"
onClick={handleCloseDialog}
className=""
>
Dismiss
</Button>
Expand Down
2 changes: 1 addition & 1 deletion src/components/UI/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const DialogClose = ({
...props
}: DialogPrimitive.DialogCloseProps & React.RefAttributes<HTMLButtonElement> & { className?: string }) => (
<DialogPrimitive.Close
name="close"
aria-label="Close"
className={cn(
"absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-slate-100 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900",
className,
Expand Down