You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
266 lines
8.0 KiB
266 lines
8.0 KiB
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import { render } from "@testing-library/react";
|
|
import { ReferenceEdge } from "@/components/edges/ReferenceEdge";
|
|
import { ReactFlowProvider, Position } from "@xyflow/react";
|
|
|
|
// Mock the workflow store
|
|
const mockUseWorkflowStore = vi.fn();
|
|
|
|
vi.mock("@/store/workflowStore", () => ({
|
|
useWorkflowStore: (selector?: (state: unknown) => unknown) => {
|
|
if (selector) {
|
|
return mockUseWorkflowStore(selector);
|
|
}
|
|
return mockUseWorkflowStore((s: unknown) => s);
|
|
},
|
|
}));
|
|
|
|
// Wrapper component for React Flow context
|
|
function TestWrapper({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<ReactFlowProvider>
|
|
<svg data-testid="svg-container">{children}</svg>
|
|
</ReactFlowProvider>
|
|
);
|
|
}
|
|
|
|
// Default edge props
|
|
const createDefaultProps = (overrides = {}) => ({
|
|
id: "ref-edge-1",
|
|
source: "node-1",
|
|
target: "node-2",
|
|
sourceX: 100,
|
|
sourceY: 50,
|
|
targetX: 300,
|
|
targetY: 50,
|
|
sourcePosition: Position.Right,
|
|
targetPosition: Position.Left,
|
|
selected: false,
|
|
...overrides,
|
|
});
|
|
|
|
// Default store state factory
|
|
const createDefaultState = (overrides = {}) => ({
|
|
edgeStyle: "angular" as const,
|
|
nodes: [],
|
|
...overrides,
|
|
});
|
|
|
|
describe("ReferenceEdge", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
// Default mock implementation
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState());
|
|
});
|
|
});
|
|
|
|
describe("Basic Rendering", () => {
|
|
it("should render the edge path", () => {
|
|
const { container } = render(
|
|
<TestWrapper>
|
|
<ReferenceEdge {...createDefaultProps()} />
|
|
</TestWrapper>
|
|
);
|
|
|
|
const paths = container.querySelectorAll("path");
|
|
expect(paths.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("should always use bezier (curved) path style", () => {
|
|
// ReferenceEdge always uses curved paths regardless of edgeStyle setting
|
|
const { container } = render(
|
|
<TestWrapper>
|
|
<ReferenceEdge {...createDefaultProps()} />
|
|
</TestWrapper>
|
|
);
|
|
|
|
const paths = container.querySelectorAll("path");
|
|
expect(paths.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("should render invisible interaction path for easier selection", () => {
|
|
const { container } = render(
|
|
<TestWrapper>
|
|
<ReferenceEdge {...createDefaultProps()} />
|
|
</TestWrapper>
|
|
);
|
|
|
|
const interactionPath = container.querySelector(".react-flow__edge-interaction");
|
|
expect(interactionPath).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe("Dashed Style", () => {
|
|
it("should render with dashed stroke pattern", () => {
|
|
const { container } = render(
|
|
<TestWrapper>
|
|
<ReferenceEdge {...createDefaultProps()} />
|
|
</TestWrapper>
|
|
);
|
|
|
|
// The BaseEdge path should have strokeDasharray style
|
|
// The style is applied via the style prop to BaseEdge
|
|
const paths = container.querySelectorAll("path");
|
|
// At least one path should exist for the dashed edge
|
|
expect(paths.length).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe("Gray Color", () => {
|
|
it("should render with gray color gradient", () => {
|
|
const { container } = render(
|
|
<TestWrapper>
|
|
<ReferenceEdge {...createDefaultProps()} />
|
|
</TestWrapper>
|
|
);
|
|
|
|
// Edges now reference shared gradient IDs instead of per-edge defs
|
|
const basePath = container.querySelector(".react-flow__edge-path");
|
|
const stroke = basePath?.getAttribute("style") ?? "";
|
|
expect(stroke).toContain("edge-grad-reference-");
|
|
});
|
|
});
|
|
|
|
describe("Read-Only (No Interactive Elements)", () => {
|
|
it("should not render any interactive draggable handles", () => {
|
|
const { container } = render(
|
|
<TestWrapper>
|
|
<ReferenceEdge
|
|
{...createDefaultProps({
|
|
selected: true,
|
|
})}
|
|
/>
|
|
</TestWrapper>
|
|
);
|
|
|
|
// No draggable handle circles should be rendered
|
|
const circles = container.querySelectorAll("circle");
|
|
expect(circles.length).toBe(0);
|
|
});
|
|
|
|
it("should not render pause indicator", () => {
|
|
const { container } = render(
|
|
<TestWrapper>
|
|
<ReferenceEdge {...createDefaultProps()} />
|
|
</TestWrapper>
|
|
);
|
|
|
|
// No rectangles (pause bars) should be rendered
|
|
const rects = container.querySelectorAll("rect");
|
|
expect(rects.length).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("Selection State", () => {
|
|
it("should have brighter opacity when connected to selected node", () => {
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState({
|
|
nodes: [{ id: "node-1", selected: true }],
|
|
}));
|
|
});
|
|
|
|
const { container } = render(
|
|
<TestWrapper>
|
|
<ReferenceEdge {...createDefaultProps()} />
|
|
</TestWrapper>
|
|
);
|
|
|
|
// Should reference the "active" shared gradient
|
|
const basePath = container.querySelector(".react-flow__edge-path");
|
|
const stroke = basePath?.getAttribute("style") ?? "";
|
|
expect(stroke).toContain("-active");
|
|
});
|
|
|
|
it("should have dimmed opacity when not connected to selected node", () => {
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState({
|
|
nodes: [{ id: "node-3", selected: true }], // Different node selected
|
|
}));
|
|
});
|
|
|
|
const { container } = render(
|
|
<TestWrapper>
|
|
<ReferenceEdge {...createDefaultProps()} />
|
|
</TestWrapper>
|
|
);
|
|
|
|
// Should reference the "dimmed" shared gradient
|
|
const basePath = container.querySelector(".react-flow__edge-path");
|
|
const stroke = basePath?.getAttribute("style") ?? "";
|
|
expect(stroke).toContain("-dimmed");
|
|
});
|
|
|
|
it("should be dimmed when no nodes are selected", () => {
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState({
|
|
nodes: [], // No nodes at all
|
|
}));
|
|
});
|
|
|
|
const { container } = render(
|
|
<TestWrapper>
|
|
<ReferenceEdge {...createDefaultProps()} />
|
|
</TestWrapper>
|
|
);
|
|
|
|
// Should reference the "dimmed" shared gradient
|
|
const basePath = container.querySelector(".react-flow__edge-path");
|
|
const stroke = basePath?.getAttribute("style") ?? "";
|
|
expect(stroke).toContain("-dimmed");
|
|
});
|
|
});
|
|
|
|
describe("Stroke Width", () => {
|
|
it("should render with thinner stroke than editable edges", () => {
|
|
const { container } = render(
|
|
<TestWrapper>
|
|
<ReferenceEdge {...createDefaultProps()} />
|
|
</TestWrapper>
|
|
);
|
|
|
|
// ReferenceEdge uses strokeWidth: 2 (vs 3 for EditableEdge)
|
|
// The style is applied to the BaseEdge component
|
|
const paths = container.querySelectorAll("path");
|
|
expect(paths.length).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe("Connection to Source and Target", () => {
|
|
it("should highlight when source node is selected", () => {
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState({
|
|
nodes: [{ id: "node-1", selected: true }],
|
|
}));
|
|
});
|
|
|
|
const { container } = render(
|
|
<TestWrapper>
|
|
<ReferenceEdge {...createDefaultProps()} />
|
|
</TestWrapper>
|
|
);
|
|
|
|
const basePath = container.querySelector(".react-flow__edge-path");
|
|
const stroke = basePath?.getAttribute("style") ?? "";
|
|
expect(stroke).toContain("-active");
|
|
});
|
|
|
|
it("should highlight when target node is selected", () => {
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState({
|
|
nodes: [{ id: "node-2", selected: true }],
|
|
}));
|
|
});
|
|
|
|
const { container } = render(
|
|
<TestWrapper>
|
|
<ReferenceEdge {...createDefaultProps()} />
|
|
</TestWrapper>
|
|
);
|
|
|
|
const basePath = container.querySelector(".react-flow__edge-path");
|
|
const stroke = basePath?.getAttribute("style") ?? "";
|
|
expect(stroke).toContain("-active");
|
|
});
|
|
});
|
|
});
|
|
|