feat(FN-1655): merge fusion/fn-1655
This commit is contained in:
@@ -141,6 +141,7 @@ export function AddNodeModal({ isOpen, onClose, onSubmit, addToast }: AddNodeMod
|
||||
placeholder="Build Machine"
|
||||
disabled={isSubmitting}
|
||||
aria-invalid={Boolean(errors.name)}
|
||||
autoFocus
|
||||
/>
|
||||
{errors.name && <span className="add-node-modal__error">{errors.name}</span>}
|
||||
</label>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Plus, Server, Wifi, WifiOff, Globe, RefreshCw } from "lucide-react";
|
||||
import { Plus, Server, Wifi, WifiOff, Globe, RefreshCw, X } from "lucide-react";
|
||||
import { useNodes } from "../hooks/useNodes";
|
||||
import { useProjects } from "../hooks/useProjects";
|
||||
import type { NodeInfo, NodeUpdateInput } from "../api";
|
||||
@@ -11,9 +11,10 @@ import type { ToastType } from "../hooks/useToast";
|
||||
|
||||
interface NodesViewProps {
|
||||
addToast: (message: string, type?: ToastType) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function NodesView({ addToast }: NodesViewProps) {
|
||||
export function NodesView({ addToast, onClose }: NodesViewProps) {
|
||||
const { nodes, loading, error, refresh, register, update, unregister, healthCheck } = useNodes();
|
||||
const { projects } = useProjects();
|
||||
const [addModalOpen, setAddModalOpen] = useState(false);
|
||||
@@ -84,6 +85,13 @@ export function NodesView({ addToast }: NodesViewProps) {
|
||||
</div>
|
||||
|
||||
<div className="nodes-view-actions">
|
||||
<button
|
||||
className="btn-icon nodes-view-close"
|
||||
onClick={onClose}
|
||||
aria-label="Close nodes view"
|
||||
>
|
||||
<X size={16} />
|
||||
</button>
|
||||
<button className="btn btn-sm" onClick={() => void handleRefresh()} disabled={loading}>
|
||||
<RefreshCw size={14} className={loading ? "spin" : ""} />
|
||||
Refresh
|
||||
|
||||
@@ -87,7 +87,7 @@ describe("NodesView", () => {
|
||||
],
|
||||
}));
|
||||
|
||||
render(<NodesView addToast={vi.fn()} />);
|
||||
render(<NodesView addToast={vi.fn()} onClose={vi.fn()} />);
|
||||
|
||||
// Check node cards are rendered - use the node card class to find elements
|
||||
const nodeCards = document.querySelectorAll(".node-card");
|
||||
@@ -106,7 +106,7 @@ describe("NodesView", () => {
|
||||
it("renders empty state when there are no nodes", () => {
|
||||
mockUseNodes.mockReturnValue(makeUseNodesResult({ nodes: [] }));
|
||||
|
||||
render(<NodesView addToast={vi.fn()} />);
|
||||
render(<NodesView addToast={vi.fn()} onClose={vi.fn()} />);
|
||||
|
||||
expect(screen.getByText("No nodes are registered yet.")).toBeDefined();
|
||||
expect(screen.getByText("Add First Node")).toBeDefined();
|
||||
@@ -119,7 +119,7 @@ describe("NodesView", () => {
|
||||
it("opens Add Node modal when Add Node button is clicked", () => {
|
||||
mockUseNodes.mockReturnValue(makeUseNodesResult({ nodes: [] }));
|
||||
|
||||
render(<NodesView addToast={vi.fn()} />);
|
||||
render(<NodesView addToast={vi.fn()} onClose={vi.fn()} />);
|
||||
|
||||
fireEvent.click(screen.getByText("Add Node"));
|
||||
expect(screen.getByRole("dialog", { name: "Add Node" })).toBeDefined();
|
||||
@@ -140,7 +140,7 @@ describe("NodesView", () => {
|
||||
nodes: [makeNode({ id: "node-1", name: "Detail Node" })],
|
||||
}));
|
||||
|
||||
render(<NodesView addToast={vi.fn()} />);
|
||||
render(<NodesView addToast={vi.fn()} onClose={vi.fn()} />);
|
||||
|
||||
// Click on the node card (not the topology node)
|
||||
const nodeCard = document.querySelector(".node-card");
|
||||
@@ -167,7 +167,7 @@ describe("NodesView", () => {
|
||||
nodes: [makeNode({ id: "node-1", name: "Local Node", type: "local" })],
|
||||
}));
|
||||
|
||||
render(<NodesView addToast={vi.fn()} />);
|
||||
render(<NodesView addToast={vi.fn()} onClose={vi.fn()} />);
|
||||
|
||||
// Click on the node card to open detail modal
|
||||
const nodeCard = document.querySelector(".node-card");
|
||||
@@ -177,4 +177,17 @@ describe("NodesView", () => {
|
||||
// Modal should show "Projects (2)" - including the unassigned project
|
||||
expect(screen.getByText("Projects (2)")).toBeDefined();
|
||||
});
|
||||
|
||||
it("renders close button and calls onClose when clicked", () => {
|
||||
mockUseNodes.mockReturnValue(makeUseNodesResult({ nodes: [] }));
|
||||
|
||||
const onClose = vi.fn();
|
||||
render(<NodesView addToast={vi.fn()} onClose={onClose} />);
|
||||
|
||||
const closeButton = screen.getByRole("button", { name: "Close nodes view" });
|
||||
expect(closeButton).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(closeButton);
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user