feat(FN-3084): add active node visual states to dependency graph
The merge adds active graph node visual states (highlight, pulsing indicator, selected border) to the dependency-graph plugin with corresponding test coverage, introduces a plugin AI security scan gate and GraphTaskNode wrapper per FN-3426, and includes a small test stabilization fix alongside docum Fusion-Task-Id: FN-3084
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
||||
import type { Task } from "@fusion/core";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { GraphTaskNode } from "../GraphTaskNode";
|
||||
import { TaskCard } from "@fusion/dashboard/app/components/TaskCard";
|
||||
import { GraphTaskNode } from "../GraphTaskNode";
|
||||
|
||||
function createTask(overrides: Partial<Task> = {}): Task {
|
||||
return {
|
||||
@@ -53,22 +53,158 @@ describe("GraphTaskNode", () => {
|
||||
expect(container.querySelector(".card")?.getAttribute("draggable")).toBe("false");
|
||||
});
|
||||
|
||||
it("shows steps expanded and agent-active styling for in-progress executing tasks", () => {
|
||||
it("shows active indicator with capitalized status for in-progress executing tasks", () => {
|
||||
const props = createProps(
|
||||
createTask({
|
||||
column: "in-progress",
|
||||
status: "executing",
|
||||
steps: [{ name: "step one", status: "in-progress" }],
|
||||
}),
|
||||
);
|
||||
|
||||
const { container } = render(<GraphTaskNode {...props} />);
|
||||
const node = screen.getByTestId("graph-task-node-FN-TEST");
|
||||
expect(node.className).toContain("graph-task-node--active");
|
||||
expect(container.querySelector(".card")?.className).toContain("agent-active");
|
||||
expect(screen.getByText("Executing")).toBeTruthy();
|
||||
expect(container.querySelector(".graph-task-active-indicator")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("defaults indicator text to Executing when status is missing on in-progress tasks", () => {
|
||||
const props = createProps(createTask({ column: "in-progress", status: undefined }));
|
||||
const { container } = render(<GraphTaskNode {...props} />);
|
||||
|
||||
expect(container.querySelector(".graph-task-active-indicator")).toBeTruthy();
|
||||
expect(screen.getByText("Executing")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("does not render active indicator for non-active tasks", () => {
|
||||
const props = createProps(createTask({ column: "in-review", status: "idle" }));
|
||||
const { container } = render(<GraphTaskNode {...props} />);
|
||||
|
||||
expect(container.querySelector(".graph-task-active-indicator")).toBeFalsy();
|
||||
expect(screen.getByTestId("graph-task-node-FN-TEST").className).not.toContain("graph-task-node--active");
|
||||
});
|
||||
|
||||
it("does not render active indicator for paused in-progress tasks", () => {
|
||||
const props = createProps(createTask({ column: "in-progress", status: "executing", paused: true }));
|
||||
const { container } = render(<GraphTaskNode {...props} />);
|
||||
|
||||
expect(container.querySelector(".graph-task-active-indicator")).toBeFalsy();
|
||||
});
|
||||
|
||||
it("does not render active indicator for failed in-progress tasks", () => {
|
||||
const props = createProps(createTask({ column: "in-progress", status: "failed" }));
|
||||
const { container } = render(<GraphTaskNode {...props} />);
|
||||
|
||||
expect(container.querySelector(".graph-task-active-indicator")).toBeFalsy();
|
||||
});
|
||||
|
||||
it("sets current-step attribute for active task when current step is valid", () => {
|
||||
const props = createProps(
|
||||
createTask({
|
||||
column: "in-progress",
|
||||
status: "executing",
|
||||
steps: [
|
||||
{ name: "step one", status: "in-progress" },
|
||||
{ name: "step two", status: "pending" },
|
||||
{ name: "step one", status: "done" },
|
||||
{ name: "step two", status: "done" },
|
||||
{ name: "step three", status: "in-progress" },
|
||||
],
|
||||
currentStep: 2,
|
||||
}),
|
||||
);
|
||||
|
||||
render(<GraphTaskNode {...props} />);
|
||||
expect(screen.getByTestId("graph-task-node-FN-TEST").getAttribute("data-current-step")).toBe("2");
|
||||
});
|
||||
|
||||
it("sets current-step attribute to zero when first step is active", () => {
|
||||
const props = createProps(
|
||||
createTask({
|
||||
column: "in-progress",
|
||||
status: "executing",
|
||||
steps: [{ name: "step one", status: "in-progress" }],
|
||||
currentStep: 0,
|
||||
}),
|
||||
);
|
||||
|
||||
const { container } = render(<GraphTaskNode {...props} />);
|
||||
expect(container.querySelector(".card")?.className).toContain("agent-active");
|
||||
expect(container.querySelector(".card-steps-list")).toBeTruthy();
|
||||
render(<GraphTaskNode {...props} />);
|
||||
expect(screen.getByTestId("graph-task-node-FN-TEST").getAttribute("data-current-step")).toBe("0");
|
||||
});
|
||||
|
||||
it("omits current-step attribute when current step is out of bounds", () => {
|
||||
const props = createProps(
|
||||
createTask({
|
||||
column: "in-progress",
|
||||
status: "executing",
|
||||
steps: [{ name: "step one", status: "in-progress" }],
|
||||
currentStep: 10,
|
||||
}),
|
||||
);
|
||||
|
||||
render(<GraphTaskNode {...props} />);
|
||||
expect(screen.getByTestId("graph-task-node-FN-TEST").hasAttribute("data-current-step")).toBe(false);
|
||||
});
|
||||
|
||||
it("omits current-step attribute when current step is negative", () => {
|
||||
const props = createProps(createTask({ column: "in-progress", status: "executing", steps: [], currentStep: -1 }));
|
||||
|
||||
render(<GraphTaskNode {...props} />);
|
||||
expect(screen.getByTestId("graph-task-node-FN-TEST").hasAttribute("data-current-step")).toBe(false);
|
||||
});
|
||||
|
||||
it("omits current-step attribute when current step is undefined", () => {
|
||||
const props = createProps(createTask({ column: "in-progress", status: "executing", steps: [], currentStep: undefined }));
|
||||
|
||||
render(<GraphTaskNode {...props} />);
|
||||
expect(screen.getByTestId("graph-task-node-FN-TEST").hasAttribute("data-current-step")).toBe(false);
|
||||
});
|
||||
|
||||
it("does not set current-step for non-active tasks", () => {
|
||||
const props = createProps(
|
||||
createTask({
|
||||
column: "todo",
|
||||
status: "queued",
|
||||
steps: [{ name: "step one", status: "in-progress" }],
|
||||
currentStep: 0,
|
||||
}),
|
||||
);
|
||||
|
||||
render(<GraphTaskNode {...props} />);
|
||||
expect(screen.getByTestId("graph-task-node-FN-TEST").hasAttribute("data-current-step")).toBe(false);
|
||||
});
|
||||
|
||||
it("sets current-step to native step index when workflow steps are present", () => {
|
||||
const props = createProps(
|
||||
createTask({
|
||||
column: "in-progress",
|
||||
status: "executing",
|
||||
steps: [
|
||||
{ id: "native-1", name: "native one", status: "done" },
|
||||
{ id: "native-2", name: "native two", status: "in-progress" },
|
||||
],
|
||||
enabledWorkflowSteps: ["wf-1"],
|
||||
currentStep: 1,
|
||||
}),
|
||||
);
|
||||
|
||||
render(<GraphTaskNode {...props} />);
|
||||
expect(screen.getByTestId("graph-task-node-FN-TEST").getAttribute("data-current-step")).toBe("1");
|
||||
});
|
||||
|
||||
it("does not set current-step when native step list is empty even with workflow steps", () => {
|
||||
const props = createProps(
|
||||
createTask({
|
||||
column: "in-progress",
|
||||
status: "executing",
|
||||
steps: [],
|
||||
enabledWorkflowSteps: ["wf-1"],
|
||||
currentStep: 0,
|
||||
}),
|
||||
);
|
||||
|
||||
render(<GraphTaskNode {...props} />);
|
||||
expect(screen.getByTestId("graph-task-node-FN-TEST").hasAttribute("data-current-step")).toBe(false);
|
||||
});
|
||||
|
||||
it("clicking card opens task detail", () => {
|
||||
|
||||
Reference in New Issue
Block a user