diff --git a/packages/serverless-workflow-diagram-editor/src/core/autoLayout.ts b/packages/serverless-workflow-diagram-editor/src/core/autoLayout.ts index b30de77..e270e5c 100644 --- a/packages/serverless-workflow-diagram-editor/src/core/autoLayout.ts +++ b/packages/serverless-workflow-diagram-editor/src/core/autoLayout.ts @@ -19,7 +19,7 @@ import { ExtendedGraph, Position, Size } from "./graph"; // Defaults export const DEFAULT_NODE_SIZE = { height: 60, - width: 180, + width: 200, }; export function applyAutoLayout(graph: ExtendedGraph): ExtendedGraph { diff --git a/packages/serverless-workflow-diagram-editor/src/core/index.ts b/packages/serverless-workflow-diagram-editor/src/core/index.ts index 800557d..c1be979 100644 --- a/packages/serverless-workflow-diagram-editor/src/core/index.ts +++ b/packages/serverless-workflow-diagram-editor/src/core/index.ts @@ -17,3 +17,4 @@ export * from "./workflowSdk"; export * from "./graph"; export * from "./autoLayout"; +export * from "./taskSubType"; diff --git a/packages/serverless-workflow-diagram-editor/src/core/taskSubType.ts b/packages/serverless-workflow-diagram-editor/src/core/taskSubType.ts new file mode 100644 index 0000000..aae4736 --- /dev/null +++ b/packages/serverless-workflow-diagram-editor/src/core/taskSubType.ts @@ -0,0 +1,53 @@ +/* + * Copyright 2021-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { GraphNodeType, type Specification } from "@serverlessworkflow/sdk"; + +export function getCallSubType(task: Specification.CallTask): string | undefined { + return typeof task.call === "string" ? task.call : undefined; +} + +export function getRunSubType(task: Specification.RunTask): string | undefined { + const run = task.run; + if (run && typeof run === "object") { + const firstKey = Object.keys(run)[0]; + return firstKey ?? undefined; + } + return undefined; +} + +export function getListenSubType(task: Specification.ListenTask): string | undefined { + const listen = task.listen?.to; + if (listen && typeof listen === "object") { + const firstKey = Object.keys(listen)[0]; + return firstKey ?? undefined; + } + return undefined; +} + +/* TODO: Add container subtypes when container nodes are available. This is the entry point to be called when we remove hardcoded values in Diagram.tsx */ +export function getTaskSubType(nodeType: GraphNodeType, task: Specification.Task): string | undefined { + switch (nodeType) { + case GraphNodeType.Call: + return getCallSubType(task as Specification.CallTask); + case GraphNodeType.Run: + return getRunSubType(task as Specification.RunTask); + case GraphNodeType.Listen: + return getListenSubType(task as Specification.ListenTask); + default: + return undefined; + } +} diff --git a/packages/serverless-workflow-diagram-editor/src/react-flow/diagram/Diagram.css b/packages/serverless-workflow-diagram-editor/src/react-flow/diagram/Diagram.css index b9deba5..c1b0011 100644 --- a/packages/serverless-workflow-diagram-editor/src/react-flow/diagram/Diagram.css +++ b/packages/serverless-workflow-diagram-editor/src/react-flow/diagram/Diagram.css @@ -198,22 +198,25 @@ @apply dec:flex dec:items-center dec:gap-3 - dec:px-4 - dec:py-3; + dec:px-3 + dec:py-2; } .dec-root .dec-task-node-icon { + @apply dec:shrink-0; color: var(--task-node-color); } .dec-root .dec-task-node-label { @apply dec:flex dec:flex-col - dec:gap-0.5; + dec:gap-0.5 + dec:min-w-0; } .dec-root .dec-task-node-name { - @apply dec:text-sm + @apply dec:truncate + dec:text-sm dec:text-black dec:leading-tight; } @@ -232,4 +235,28 @@ .dec-root.dark .dec-task-node-type { @apply dec:text-gray-400; } + + .dec-root .dec-task-node-badge { + @apply dec:ml-auto + dec:shrink-0 + dec:rounded + dec:px-2 + dec:py-0.5 + dec:text-[8px] + dec:font-semibold + dec:uppercase + dec:whitespace-nowrap; + color: var(--task-node-color); + border: 1px solid var(--task-node-color); + } + + .dec-root .dec-task-node-badge-icon { + @apply dec:ml-auto + dec:shrink-0 + dec:flex + dec:items-center + dec:justify-center; + color: var(--task-node-color); + } + /* end task leaf nodes */ } diff --git a/packages/serverless-workflow-diagram-editor/src/react-flow/diagram/Diagram.tsx b/packages/serverless-workflow-diagram-editor/src/react-flow/diagram/Diagram.tsx index 64a24ce..ce6a71f 100644 --- a/packages/serverless-workflow-diagram-editor/src/react-flow/diagram/Diagram.tsx +++ b/packages/serverless-workflow-diagram-editor/src/react-flow/diagram/Diagram.tsx @@ -39,7 +39,7 @@ const initialNodes: RF.Node[] = [ position: { x: 100, y: 0 }, height: DEFAULT_NODE_SIZE.height, width: DEFAULT_NODE_SIZE.width, - data: { label: "CallNode" }, + data: { label: "CallNode", badge: "HTTP" }, }, { id: "n2", @@ -60,7 +60,7 @@ const initialNodes: RF.Node[] = [ { id: "n4", type: GraphNodeType.Emit, - position: { x: -100, y: 300 }, + position: { x: -150, y: 300 }, height: DEFAULT_NODE_SIZE.height, width: DEFAULT_NODE_SIZE.width, data: { label: "EmitNode" }, @@ -76,7 +76,7 @@ const initialNodes: RF.Node[] = [ { id: "n6", type: GraphNodeType.Fork, - position: { x: 300, y: 300 }, + position: { x: 350, y: 300 }, height: DEFAULT_NODE_SIZE.height, width: DEFAULT_NODE_SIZE.width, data: { label: "Node 6" }, @@ -87,7 +87,7 @@ const initialNodes: RF.Node[] = [ position: { x: 100, y: 400 }, height: DEFAULT_NODE_SIZE.height, width: DEFAULT_NODE_SIZE.width, - data: { label: "ListenNode" }, + data: { label: "ListenNode", badge: "ALL" }, }, { id: "n8", @@ -103,7 +103,7 @@ const initialNodes: RF.Node[] = [ position: { x: 100, y: 600 }, height: DEFAULT_NODE_SIZE.height, width: DEFAULT_NODE_SIZE.width, - data: { label: "RunNode" }, + data: { label: "RunNode", badge: "myCustomType" }, }, { id: "n10", @@ -139,12 +139,11 @@ const initialEdges: RF.Edge[] = [ type: GraphEdgeType.Default, data: { wayPoints: [ - { x: 190, y: 60 }, - { x: 190, y: 70 }, + { x: 200, y: 70 }, { x: 140, y: 70 }, { x: 140, y: 85 }, - { x: 190, y: 85 }, - { x: 190, y: 95 }, + { x: 200, y: 85 }, + { x: 200, y: 95 }, ], }, }, diff --git a/packages/serverless-workflow-diagram-editor/src/react-flow/nodes/Nodes.tsx b/packages/serverless-workflow-diagram-editor/src/react-flow/nodes/Nodes.tsx index d5f7bb0..9882560 100644 --- a/packages/serverless-workflow-diagram-editor/src/react-flow/nodes/Nodes.tsx +++ b/packages/serverless-workflow-diagram-editor/src/react-flow/nodes/Nodes.tsx @@ -18,6 +18,7 @@ import type React from "react"; import { GraphNodeType } from "@serverlessworkflow/sdk"; import * as RF from "@xyflow/react"; import { type LeafNodeType, taskNodeConfigMap } from "./taskNodeConfig"; +import { Info } from "lucide-react"; // Node types must match sdk GraphNodeType enum export const NodeTypes: RF.NodeTypes = { @@ -31,13 +32,32 @@ export const NodeTypes: RF.NodeTypes = { [GraphNodeType.Run]: RunNode, [GraphNodeType.Set]: SetNode, [GraphNodeType.Switch]: SwitchNode, + [GraphNodeType.TryCatch]: TryCatchNode, [GraphNodeType.Try]: TryNode, + [GraphNodeType.Catch]: CatchNode, [GraphNodeType.Wait]: WaitNode, }; +const KNOWN_BADGES = new Set([ + "http", + "grpc", + "asyncapi", + "openapi", + "a2a", + "mcp", + "container", + "script", + "shell", + "workflow", + "all", + "any", + "one", +]); + export type BaseNodeData = { // TODO: It is a placeholder, add properties to be consumed internally by node components label: string; + badge?: string; }; interface NodeContentProps { @@ -47,6 +67,30 @@ interface NodeContentProps { type: string; } +interface BadgeProps { + badge: string; + testId: string; +} + +function TaskNodeBadge({ badge, testId }: BadgeProps) { + const isUnknown = !KNOWN_BADGES.has(badge.toLowerCase()); + + if (isUnknown) { + /* TODO: instead of using the browser default to display tool tip like below, replace with tooltip component when we add it */ + return ( + + + + ); + } + + return ( + + {badge} + + ); +} + function TaskNodeContent({ id, data, selected, type }: NodeContentProps) { const config = taskNodeConfigMap[type as LeafNodeType]; const Icon = config.icon; @@ -63,6 +107,7 @@ function TaskNodeContent({ id, data, selected, type }: NodeContentProps) { {data.label} {config.typeLabel} + {data.badge && } @@ -93,77 +138,90 @@ function PlaceholderContent({ id, data, selected, type }: PlaceholderProps) { ); } -/* call node */ +/* call leaf node */ export type CallNodeType = RF.Node; export function CallNode({ id, data, selected, type }: RF.NodeProps) { return ; } -/* do node */ +/* do container node */ export type DoNodeType = RF.Node; export function DoNode({ id, data, selected, type }: RF.NodeProps) { // TODO: This component is just a placeholder return ; } -/* emit node */ +/* emit leaf node */ export type EmitNodeType = RF.Node; export function EmitNode({ id, data, selected, type }: RF.NodeProps) { return ; } -/* for node */ +/* for container node */ export type ForNodeType = RF.Node; export function ForNode({ id, data, selected, type }: RF.NodeProps) { // TODO: This component is just a placeholder return ; } -/* fork node */ +/* fork container node */ export type ForkNodeType = RF.Node; export function ForkNode({ id, data, selected, type }: RF.NodeProps) { // TODO: This component is just a placeholder return ; } -/* listen node */ +/* listen leaf node */ export type ListenNodeType = RF.Node; export function ListenNode({ id, data, selected, type }: RF.NodeProps) { return ; } -/* raise node */ +/* raise leaf node */ export type RaiseNodeType = RF.Node; export function RaiseNode({ id, data, selected, type }: RF.NodeProps) { return ; } -/* run node */ +/* run leaf node */ export type RunNodeType = RF.Node; export function RunNode({ id, data, selected, type }: RF.NodeProps) { return ; } -/* set node */ +/* set leaf node */ export type SetNodeType = RF.Node; export function SetNode({ id, data, selected, type }: RF.NodeProps) { return ; } -/* switch node */ +/* switch leaf node */ export type SwitchNodeType = RF.Node; export function SwitchNode({ id, data, selected, type }: RF.NodeProps) { return ; } -/* try node */ +/* try catch container node */ +export type TryCatchNodeType = RF.Node; +export function TryCatchNode({ id, data, selected, type }: RF.NodeProps) { + // TODO: This component is just a placeholder + return ; +} + +/* try container node */ export type TryNodeType = RF.Node; export function TryNode({ id, data, selected, type }: RF.NodeProps) { // TODO: This component is just a placeholder return ; } -/* wait node */ +/* catch leaf node */ +export type CatchNodeType = RF.Node; +export function CatchNode({ id, data, selected, type }: RF.NodeProps) { + return ; +} + +/* wait leaf node */ export type WaitNodeType = RF.Node; export function WaitNode({ id, data, selected, type }: RF.NodeProps) { return ; diff --git a/packages/serverless-workflow-diagram-editor/src/react-flow/nodes/taskNodeConfig.ts b/packages/serverless-workflow-diagram-editor/src/react-flow/nodes/taskNodeConfig.ts index b71a715..5580651 100644 --- a/packages/serverless-workflow-diagram-editor/src/react-flow/nodes/taskNodeConfig.ts +++ b/packages/serverless-workflow-diagram-editor/src/react-flow/nodes/taskNodeConfig.ts @@ -23,12 +23,14 @@ import { Megaphone, PenLine, Phone, + ShieldAlert, Terminal, } from "lucide-react"; import type { ComponentType } from "react"; export type LeafNodeType = | typeof GraphNodeType.Call + | typeof GraphNodeType.Catch | typeof GraphNodeType.Emit | typeof GraphNodeType.Listen | typeof GraphNodeType.Raise @@ -49,6 +51,11 @@ export const taskNodeConfigMap: Record = { icon: Phone, typeLabel: "CALL", }, + [GraphNodeType.Catch]: { + color: "#F97316", + icon: ShieldAlert, + typeLabel: "CATCH", + }, [GraphNodeType.Emit]: { color: "#8B5CF6", icon: Megaphone, diff --git a/packages/serverless-workflow-diagram-editor/tests/core/taskSubType.test.ts b/packages/serverless-workflow-diagram-editor/tests/core/taskSubType.test.ts new file mode 100644 index 0000000..76aeb5e --- /dev/null +++ b/packages/serverless-workflow-diagram-editor/tests/core/taskSubType.test.ts @@ -0,0 +1,124 @@ +/* + * Copyright 2021-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { GraphNodeType, type Specification } from "@serverlessworkflow/sdk"; +import { describe, expect, it } from "vitest"; +import { getCallSubType, getListenSubType, getRunSubType, getTaskSubType } from "../../src/core"; + +describe("getTaskSubType", () => { + it("should call getCallSubType for CallTask", () => { + const task = { call: "http" } as Specification.CallTask; + expect(getTaskSubType(GraphNodeType.Call, task)).toBe("http"); + }); + + it("should call getRunSubType for RunTask", () => { + const task = { run: { script: {} } } as Specification.RunTask; + expect(getTaskSubType(GraphNodeType.Run, task)).toBe("script"); + }); + + it("should call getListenSubType for ListenTask", () => { + const task = { listen: { to: { any: {} } } } as Specification.ListenTask; + expect(getTaskSubType(GraphNodeType.Listen, task)).toBe("any"); + }); + + it("should return undefined for nodes without subtypes", () => { + expect(getTaskSubType(GraphNodeType.Emit, {} as Specification.Task)).toBeUndefined(); + expect(getTaskSubType(GraphNodeType.Raise, {} as Specification.Task)).toBeUndefined(); + expect(getTaskSubType(GraphNodeType.Set, {} as Specification.Task)).toBeUndefined(); + expect(getTaskSubType(GraphNodeType.Switch, {} as Specification.Task)).toBeUndefined(); + expect(getTaskSubType(GraphNodeType.Try, {} as Specification.Task)).toBeUndefined(); + expect(getTaskSubType(GraphNodeType.Wait, {} as Specification.Task)).toBeUndefined(); + }); +}); + +describe("getCallSubType", () => { + it.each([ + ["http", "http"], + ["grpc", "grpc"], + ["asyncapi", "asyncapi"], + ["openapi", "openapi"], + ["a2a", "a2a"], + ["mcp", "mcp"], + ])("should return %s correct for known call subtype '%s':", (callValue, expectedSubType) => { + const task = { call: callValue } as Specification.CallTask; + expect(getCallSubType(task)).toBe(expectedSubType); + }); + + it("should return the custom function name for unknown subtypes", () => { + const task = { call: "myCustomFunction" } as Specification.CallTask; + expect(getCallSubType(task)).toBe("myCustomFunction"); + }); + + it("should return undefined when call is missing", () => { + const task = {} as Specification.CallTask; + expect(getCallSubType(task)).toBeUndefined(); + }); +}); + +describe("getRunSubType", () => { + it.each([ + ["container", "container"], + ["script", "script"], + ["shell", "shell"], + ["workflow", "workflow"], + ])("should return '%s' run key subtype %s", (runKey, expectedSubType) => { + const task = { run: { [runKey]: {} } } as Specification.RunTask; + expect(getRunSubType(task)).toBe(expectedSubType); + }); + + it("should return unknown run types", () => { + const task = { run: { unknownRunType: {} } } as unknown as Specification.RunTask; + expect(getRunSubType(task)).toBe("unknownRunType"); + }); + + it("should return undefined when run is missing", () => { + const task = {} as Specification.RunTask; + expect(getRunSubType(task)).toBeUndefined(); + }); + + it("should return undefined when run is not an object", () => { + const task = { run: "invalidRunValue" } as unknown as Specification.RunTask; + expect(getRunSubType(task)).toBeUndefined(); + }); +}); + +describe("getListenSubType", () => { + it.each([ + ["all", "all"], + ["any", "any"], + ["one", "one"], + ])("should return '%s' listen key subtype %s", (listenKey, expectedSubType) => { + const task = { listen: { to: { [listenKey]: {} } } } as Specification.ListenTask; + expect(getListenSubType(task)).toBe(expectedSubType); + }); + + it("should return unknown listen types", () => { + const task = { + listen: { to: { unknownListenType: {} } }, + } as unknown as Specification.ListenTask; + expect(getListenSubType(task)).toBe("unknownListenType"); + }); + + it("should return undefined when listen is missing", () => { + const task = {} as Specification.ListenTask; + expect(getListenSubType(task)).toBeUndefined(); + }); + + it("should return undefined when listen.to is missing", () => { + const task = { listen: {} } as Specification.ListenTask; + expect(getListenSubType(task)).toBeUndefined(); + }); +}); diff --git a/packages/serverless-workflow-diagram-editor/tests/react-flow/nodes/Nodes.test.tsx b/packages/serverless-workflow-diagram-editor/tests/react-flow/nodes/Nodes.test.tsx index b5a1896..7af8412 100644 --- a/packages/serverless-workflow-diagram-editor/tests/react-flow/nodes/Nodes.test.tsx +++ b/packages/serverless-workflow-diagram-editor/tests/react-flow/nodes/Nodes.test.tsx @@ -22,14 +22,14 @@ import { NodeTypes } from "../../../src/react-flow/nodes/Nodes"; import { DEFAULT_NODE_SIZE } from "../../../src/core"; import { taskNodeConfigMap, type LeafNodeType } from "../../../src/react-flow/nodes/taskNodeConfig"; -function testNode(id: string, type: string, y: number, label: string): RF.Node { +function testNode(id: string, type: string, y: number, label: string, badge?: string): RF.Node { return { id, type, position: { x: 100, y }, height: DEFAULT_NODE_SIZE.height, width: DEFAULT_NODE_SIZE.width, - data: { label }, + data: { label, ...(badge !== undefined ? { badge } : {}) }, }; } @@ -44,24 +44,26 @@ const allNodes: RF.Node[] = [ testNode("n8", GraphNodeType.Raise, 700, "Node 8"), testNode("n9", GraphNodeType.Run, 800, "Node 9"), testNode("n10", GraphNodeType.Set, 900, "Node 10"), - testNode("n11", GraphNodeType.Try, 1000, "Node 11"), - testNode("n12", GraphNodeType.Wait, 1100, "Node 12"), + testNode("n11", GraphNodeType.TryCatch, 1000, "Node 11"), + testNode("n12", GraphNodeType.Try, 1100, "Node 12"), + testNode("n13", GraphNodeType.Catch, 1200, "Node 13"), + testNode("n14", GraphNodeType.Wait, 1300, "Node 14"), ]; const allEdges: RF.Edge[] = [ { id: "n1-n2", source: "n1", target: "n2" }, { id: "n2-n3", source: "n2", target: "n3" }, { id: "n3-n4", source: "n3", target: "n4" }, - { id: "n3-n5", source: "n3", target: "n5" }, - { id: "n3-n6", source: "n3", target: "n6" }, - { id: "n4-n7", source: "n4", target: "n7" }, - { id: "n5-n7", source: "n5", target: "n7" }, + { id: "n4-n5", source: "n4", target: "n5" }, + { id: "n5-n6", source: "n5", target: "n6" }, { id: "n6-n7", source: "n6", target: "n7" }, { id: "n7-n8", source: "n7", target: "n8" }, { id: "n8-n9", source: "n8", target: "n9" }, { id: "n9-n10", source: "n9", target: "n10" }, { id: "n10-n11", source: "n10", target: "n11" }, { id: "n11-n12", source: "n11", target: "n12" }, + { id: "n12-n13", source: "n12", target: "n13" }, + { id: "n13-n14", source: "n13", target: "n14" }, ]; describe("React Flow custom node types", () => { @@ -86,8 +88,10 @@ describe("React Flow custom node types", () => { expect(screen.getByTestId("raise-node-n8")).toBeInTheDocument(); expect(screen.getByTestId("run-node-n9")).toBeInTheDocument(); expect(screen.getByTestId("set-node-n10")).toBeInTheDocument(); - expect(screen.getByTestId("try-node-n11")).toBeInTheDocument(); - expect(screen.getByTestId("wait-node-n12")).toBeInTheDocument(); + expect(screen.getByTestId("try-catch-node-n11")).toBeInTheDocument(); + expect(screen.getByTestId("try-node-n12")).toBeInTheDocument(); + expect(screen.getByTestId("catch-node-n13")).toBeInTheDocument(); + expect(screen.getByTestId("wait-node-n14")).toBeInTheDocument(); }); describe("should render leaf nodes with TaskNodeContent", () => { @@ -99,7 +103,8 @@ describe("React Flow custom node types", () => { { id: "n8", type: GraphNodeType.Raise, testId: "raise" }, { id: "n9", type: GraphNodeType.Run, testId: "run" }, { id: "n10", type: GraphNodeType.Set, testId: "set" }, - { id: "n12", type: GraphNodeType.Wait, testId: "wait" }, + { id: "n13", type: GraphNodeType.Catch, testId: "catch" }, + { id: "n14", type: GraphNodeType.Wait, testId: "wait" }, ]; it.each(leafNodes)("should render %s node with correct config", ({ id, type, testId }) => { @@ -119,4 +124,56 @@ describe("React Flow custom node types", () => { expect(node.style.getPropertyValue("--task-node-color")).toBe(config.color); }); }); + + describe("badge rendering", () => { + it("should render a text badge for known subtypes", () => { + const nodesWithBadges = [ + testNode("n1", GraphNodeType.Call, 10, "CallNode", "HTTP"), + testNode("n2", GraphNodeType.Listen, 100, "ListenNode", "ANY"), + ]; + render( +
+ +
, + ); + + const callBadge = screen.getByTestId("call-node-n1-badge"); + expect(callBadge).toBeInTheDocument(); + expect(callBadge.textContent).toBe("HTTP"); + + const listenBadge = screen.getByTestId("listen-node-n2-badge"); + expect(listenBadge).toBeInTheDocument(); + expect(listenBadge.textContent).toBe("ANY"); + }); + + it("should render an icon badge for an unknown subtype", () => { + const nodesWithUnknownBadges = [ + testNode("n1", GraphNodeType.Call, 100, "CallNode", "UnknownCall"), + testNode("n2", GraphNodeType.Listen, 100, "ListenNode", "UnknownListen"), + ]; + render( +
+ +
, + ); + + const callBadge = screen.getByTestId("call-node-n1-badge-icon"); + expect(callBadge).toBeInTheDocument(); + + const listenBadge = screen.getByTestId("listen-node-n2-badge-icon"); + expect(listenBadge).toBeInTheDocument(); + }); + + it("should not render a badge when it is not present", () => { + const nodesWithoutBadges = [testNode("n1", GraphNodeType.Wait, 100, "WaitNode")]; + render( +
+ +
, + ); + + const badge = screen.queryByTestId("wait-node-n1-badge"); + expect(badge).not.toBeInTheDocument(); + }); + }); }); diff --git a/packages/serverless-workflow-diagram-editor/tests/react-flow/nodes/taskNodeConfig.test.ts b/packages/serverless-workflow-diagram-editor/tests/react-flow/nodes/taskNodeConfig.test.ts index 19ff1e4..6f0b6b9 100644 --- a/packages/serverless-workflow-diagram-editor/tests/react-flow/nodes/taskNodeConfig.test.ts +++ b/packages/serverless-workflow-diagram-editor/tests/react-flow/nodes/taskNodeConfig.test.ts @@ -27,6 +27,7 @@ const leafNodeTypes: LeafNodeType[] = [ GraphNodeType.Set, GraphNodeType.Switch, GraphNodeType.Wait, + GraphNodeType.Catch, ]; describe("taskNodeConfig", () => {