Browse Source

fix: improvements to refresh dialog

pull/571/head
Dan Ditomaso 1 year ago
parent
commit
d53ababf7d
  1. 107
      src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.test.tsx
  2. 30
      src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.tsx
  3. 2
      src/components/UI/Dialog.tsx

107
src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.test.tsx

@ -1,55 +1,102 @@
import { render, screen, fireEvent } from "@testing-library/react"; import { render, screen, fireEvent } from "@testing-library/react";
import { beforeEach, describe, expect, it, Mock, vi } from "vitest"; import { beforeEach, describe, expect, it, vi, Mock } from "vitest";
import { RefreshKeysDialog } from "./RefreshKeysDialog"; import { RefreshKeysDialog } from "./RefreshKeysDialog.tsx";
import { useRefreshKeysDialog } from "./useRefreshKeysDialog.ts"; 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", () => ({ vi.mock("./useRefreshKeysDialog.ts", () => ({
useRefreshKeysDialog: vi.fn(), useRefreshKeysDialog: vi.fn(() => ({
handleCloseDialog: mockHandleCloseDialog,
handleNodeRemove: mockHandleNodeRemove,
})),
})); }));
describe("RefreshKeysDialog Component", () => { describe("RefreshKeysDialog Component", () => {
let handleCloseDialogMock: Mock;
let handleNodeRemoveMock: Mock;
let onOpenChangeMock: Mock; let onOpenChangeMock: Mock;
beforeEach(() => { beforeEach(() => {
handleCloseDialogMock = vi.fn(); vi.clearAllMocks();
handleNodeRemoveMock = vi.fn();
onOpenChangeMock = vi.fn(); onOpenChangeMock = vi.fn();
(useRefreshKeysDialog as Mock).mockReturnValue({ vi.mocked(useMessageStore).mockReturnValue({ activeChat: 123 });
handleCloseDialog: handleCloseDialogMock, vi.mocked(useDevice).mockReturnValue({
handleNodeRemove: handleNodeRemoveMock, nodeErrors: mockNodeErrors,
nodes: mockNodes,
});
vi.mocked(useRefreshKeysDialog).mockReturnValue({
handleCloseDialog: mockHandleCloseDialog,
handleNodeRemove: mockHandleNodeRemove,
}); });
}); });
it("renders the dialog with correct content", () => { it("should render the dialog with dynamic content when open and data is available", () => {
render(<RefreshKeysDialog open={true} onOpenChange={onOpenChangeMock} />); render(<RefreshKeysDialog open onOpenChange={onOpenChangeMock} />);
expect(screen.getByText("Keys Mismatch")).toBeInTheDocument();
expect(screen.getByText("Request New Keys")).toBeInTheDocument(); expect(screen.getByText(`Keys Mismatch - ${mockNodeWithError?.user?.longName}`)).toBeInTheDocument();
expect(screen.getByText("Dismiss")).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", () => { it("should call handleNodeRemove when 'Request New Keys' button is clicked", () => {
render(<RefreshKeysDialog open={true} onOpenChange={onOpenChangeMock} />); render(<RefreshKeysDialog open onOpenChange={onOpenChangeMock} />);
fireEvent.click(screen.getByText("Request New Keys")); fireEvent.click(screen.getByRole('button', { name: /request new keys/i }));
expect(handleNodeRemoveMock).toHaveBeenCalled(); expect(mockHandleNodeRemove).toHaveBeenCalledTimes(1);
}); });
it("calls handleCloseDialog when 'Dismiss' button is clicked", () => { it("should call handleCloseDialog when 'Dismiss' button is clicked", () => {
render(<RefreshKeysDialog open={true} onOpenChange={onOpenChangeMock} />); render(<RefreshKeysDialog open onOpenChange={onOpenChangeMock} />);
fireEvent.click(screen.getByText("Dismiss")); fireEvent.click(screen.getByRole('button', { name: /dismiss/i }));
expect(handleCloseDialogMock).toHaveBeenCalled(); expect(mockHandleCloseDialog).toHaveBeenCalledTimes(1);
}); });
it("calls onOpenChange when dialog close button is clicked", () => { it("should call handleCloseDialog when the explicit DialogClose button is clicked", () => {
render(<RefreshKeysDialog open={true} onOpenChange={onOpenChangeMock} />); render(<RefreshKeysDialog open onOpenChange={onOpenChangeMock} />);
fireEvent.click(screen.getByRole("button", { name: /close/i })); fireEvent.click(screen.getByRole('button', { name: /close/i })); // Use the aria-label
expect(handleCloseDialogMock).toHaveBeenCalled(); 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} />); 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
src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.tsx

@ -8,6 +8,8 @@ import {
import { Button } from "@components/UI/Button.tsx"; import { Button } from "@components/UI/Button.tsx";
import { LockKeyholeOpenIcon } from "lucide-react"; import { LockKeyholeOpenIcon } from "lucide-react";
import { useRefreshKeysDialog } from "./useRefreshKeysDialog.ts"; import { useRefreshKeysDialog } from "./useRefreshKeysDialog.ts";
import { useDevice } from "@core/stores/deviceStore.ts";
import { useMessageStore } from "@core/stores/messageStore.ts";
export interface RefreshKeysDialogProps { export interface RefreshKeysDialogProps {
open: boolean; open: boolean;
@ -15,16 +17,36 @@ export interface RefreshKeysDialogProps {
} }
export const RefreshKeysDialog = ({ open, onOpenChange }: RefreshKeysDialogProps) => { export const RefreshKeysDialog = ({ open, onOpenChange }: RefreshKeysDialogProps) => {
const { activeChat } = useMessageStore();
const { nodeErrors, nodes } = useDevice();
const { handleCloseDialog, handleNodeRemove } = useRefreshKeysDialog(); 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 ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-8 flex flex-col gap-2"> <DialogContent className="max-w-8 flex flex-col gap-2">
<DialogClose onClick={handleCloseDialog} /> <DialogClose onClick={handleCloseDialog} />
<DialogHeader> <DialogHeader>
<DialogTitle>Keys Mismatch</DialogTitle> <DialogTitle>{text.title}</DialogTitle>
</DialogHeader> </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"> <ul className="mt-2">
<li className="flex place-items-center gap-2 items-start"> <li className="flex place-items-center gap-2 items-start">
<div className="p-2 bg-slate-500 rounded-lg mt-1"> <div className="p-2 bg-slate-500 rounded-lg mt-1">
@ -40,14 +62,12 @@ export const RefreshKeysDialog = ({ open, onOpenChange }: RefreshKeysDialogProps
<Button <Button
variant="default" variant="default"
onClick={handleNodeRemove} onClick={handleNodeRemove}
className=""
> >
Request New Keys Request New Keys
</Button> </Button>
<Button <Button
variant="outline" variant="outline"
onClick={handleCloseDialog} onClick={handleCloseDialog}
className=""
> >
Dismiss Dismiss
</Button> </Button>

2
src/components/UI/Dialog.tsx

@ -60,7 +60,7 @@ const DialogClose = ({
...props ...props
}: DialogPrimitive.DialogCloseProps & React.RefAttributes<HTMLButtonElement> & { className?: string }) => ( }: DialogPrimitive.DialogCloseProps & React.RefAttributes<HTMLButtonElement> & { className?: string }) => (
<DialogPrimitive.Close <DialogPrimitive.Close
name="close" aria-label="Close"
className={cn( 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", "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, className,

Loading…
Cancel
Save