diff --git a/packages/open-workflow-diagram-editor/tests/components/ui/sidebar.test.tsx b/packages/open-workflow-diagram-editor/tests/components/ui/sidebar.test.tsx
index 46cf2f1a..a006df0c 100644
--- a/packages/open-workflow-diagram-editor/tests/components/ui/sidebar.test.tsx
+++ b/packages/open-workflow-diagram-editor/tests/components/ui/sidebar.test.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { describe, it, expect } from "vitest";
+import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import {
@@ -111,4 +111,38 @@ describe("Sidebar", () => {
expect(screen.getByText("Header Text")).toBeInTheDocument();
expect(screen.getByText("Content Text")).toBeInTheDocument();
});
+
+ it("calls onOpenChange when used as a controlled component", async () => {
+ const user = userEvent.setup();
+ const onOpenChange = vi.fn();
+
+ render(
+
+
+
+
+
+
+ ,
+ );
+
+ await user.click(screen.getByRole("button", { name: /toggle sidebar/i }));
+
+ expect(onOpenChange).toHaveBeenCalledWith(true);
+ });
+
+ it("renders non-collapsible sidebar", () => {
+ const { container } = render(
+
+
+ Content
+
+ ,
+ );
+
+ const sidebar = findSidebar(container);
+
+ expect(sidebar).not.toHaveAttribute("data-state");
+ expect(sidebar).toBeInTheDocument();
+ });
});
diff --git a/packages/open-workflow-diagram-editor/tests/core/graph.test.ts b/packages/open-workflow-diagram-editor/tests/core/graph.test.ts
index a36189c2..da9112f4 100644
--- a/packages/open-workflow-diagram-editor/tests/core/graph.test.ts
+++ b/packages/open-workflow-diagram-editor/tests/core/graph.test.ts
@@ -1009,5 +1009,33 @@ describe("graph utils", () => {
expect(fixedGraph.edges[3]?.targetId).toBe("task-outside");
expect(fixedGraph.edges[3]?.id).toBe("edge-2-redirected");
});
+
+ it("does not create redirected edges when parent has no exit node", () => {
+ const parent = {
+ id: "parent",
+ type: GraphNodeType.Do,
+ } as FlatGraphNode;
+
+ const child = {
+ id: "child",
+ type: GraphNodeType.Call,
+ parentId: "parent",
+ } as FlatGraphNode;
+
+ const outside = {
+ id: "outside",
+ type: GraphNodeType.Call,
+ } as FlatGraphNode;
+
+ const graph = createFlatGraph(
+ [parent, child, outside],
+ [{ id: "e1", sourceId: "child", targetId: "outside", label: "" }],
+ );
+
+ const fixed = fixNodesConnections(graph);
+
+ expect(fixed.edges).toHaveLength(1);
+ expect(fixed.edges[0]?.targetId).toBe("outside");
+ });
});
});
diff --git a/packages/open-workflow-diagram-editor/tests/core/taskDetails.test.ts b/packages/open-workflow-diagram-editor/tests/core/taskDetails.test.ts
index f227d914..9d4e76c4 100644
--- a/packages/open-workflow-diagram-editor/tests/core/taskDetails.test.ts
+++ b/packages/open-workflow-diagram-editor/tests/core/taskDetails.test.ts
@@ -142,4 +142,158 @@ describe("getTaskDetails", () => {
it("returns no fields for a task with no displayable fields", () => {
expect(getTaskDetails(asTask({}))).toEqual([]);
});
+
+ it("ignores null and undefined values", () => {
+ const fields = getTaskDetails(
+ asTask({
+ set: {
+ a: null,
+ b: undefined,
+ c: "value",
+ },
+ }),
+ );
+
+ expect(fields).toEqual([{ path: "set.c", kind: "text", display: "value" }]);
+ });
+
+ it("converts boolean values to text", () => {
+ const fields = getTaskDetails(
+ asTask({
+ set: {
+ enabled: true,
+ disabled: false,
+ },
+ }),
+ );
+
+ expect(fields).toEqual([
+ { path: "set.enabled", kind: "text", display: "true" },
+ { path: "set.disabled", kind: "text", display: "false" },
+ ]);
+ });
+
+ it("ignores empty objects", () => {
+ const fields = getTaskDetails(
+ asTask({
+ set: {},
+ }),
+ );
+
+ expect(fields).toEqual([]);
+ });
+
+ it("ignores nested empty objects", () => {
+ const fields = getTaskDetails(
+ asTask({
+ set: {
+ foo: {},
+ },
+ }),
+ );
+
+ expect(fields).toEqual([]);
+ });
+
+ it("ignores input, output and export when they are not objects", () => {
+ const fields = getTaskDetails(
+ asTask({
+ input: "invalid",
+ output: 123,
+ export: true,
+ }),
+ );
+
+ expect(fields).toEqual([]);
+ });
+
+ it("ignores timeout object without an after property", () => {
+ const fields = getTaskDetails(
+ asTask({
+ timeout: {},
+ }),
+ );
+
+ expect(fields).toEqual([]);
+ });
+
+ it("ignores timeout object when after is undefined", () => {
+ const fields = getTaskDetails(
+ asTask({
+ timeout: {
+ after: undefined,
+ },
+ }),
+ );
+
+ expect(fields).toEqual([]);
+ });
+
+ it("supports primitive task-specific values", () => {
+ const fields = getTaskDetails(
+ asTask({
+ call: 123,
+ }),
+ );
+
+ expect(fields).toEqual([{ path: "call", kind: "text", display: "123" }]);
+ });
+
+ it("flattens fields exactly at the maximum supported depth", () => {
+ const fields = getTaskDetails(
+ asTask({
+ with: {
+ a: {
+ b: {
+ c: {
+ value: "foo",
+ },
+ },
+ },
+ },
+ }),
+ );
+
+ expect(fields).toEqual([
+ {
+ path: "with.a.b.c.value",
+ kind: "text",
+ display: "foo",
+ },
+ ]);
+ });
+
+ it("returns base fields in the expected order", () => {
+ const fields = getTaskDetails(
+ asTask({
+ if: "${ .condition }",
+ input: { from: "${ .input }" },
+ output: { as: "${ .output }" },
+ export: { as: "${ .export }" },
+ timeout: "PT5M",
+ then: "next",
+ }),
+ );
+
+ expect(fields).toEqual([
+ { path: "if", kind: "text", display: "${ .condition }" },
+ { path: "input.from", kind: "text", display: "${ .input }" },
+ { path: "output.as", kind: "text", display: "${ .output }" },
+ { path: "export.as", kind: "text", display: "${ .export }" },
+ { path: "timeout", kind: "text", display: "PT5M" },
+ { path: "then", kind: "text", display: "next" },
+ ]);
+ });
+
+ it("returns no fields when only metadata is present", () => {
+ const fields = getTaskDetails(
+ asTask({
+ metadata: {
+ author: "john",
+ },
+ }),
+ );
+
+ expect(fields).toEqual([]);
+ });
});
diff --git a/packages/open-workflow-diagram-editor/tests/core/validationErrors.test.ts b/packages/open-workflow-diagram-editor/tests/core/validationErrors.test.ts
index 892c0131..ac969adf 100644
--- a/packages/open-workflow-diagram-editor/tests/core/validationErrors.test.ts
+++ b/packages/open-workflow-diagram-editor/tests/core/validationErrors.test.ts
@@ -210,4 +210,81 @@ describe("getGeneralErrors", () => {
expect(getGeneralErrors(errors, nodeIds)).toEqual([documentErr, rawErr]);
});
+
+ describe("additional validation error edge cases", () => {
+ it("returns each node id only once even if multiple errors belong to it", () => {
+ const nodeIds = new Set(["/do/0/call"]);
+
+ const errors: SdkError[] = [
+ vErr({ path: "/do/0/call", message: "first" }),
+ vErr({ path: "/do/0/call/with", message: "second" }),
+ vErr({ path: "/do/0/call/output", message: "third" }),
+ ];
+
+ expect(getErrorNodeIds(errors, nodeIds)).toEqual(new Set(["/do/0/call"]));
+ });
+
+ it("does not treat non-string missingProperty values as noise", () => {
+ const nodeIds = new Set(["/do/0/call"]);
+
+ const errors: SdkError[] = [
+ vErr({
+ path: "/do/0/call",
+ object: {
+ missingProperty: 123,
+ },
+ }),
+ ];
+
+ const result = getNodeErrors(errors, "/do/0/call", nodeIds);
+
+ expect(result).toHaveLength(1);
+ expect(result[0]?.object?.missingProperty).toBe(123);
+ });
+
+ it("keeps validation errors without an errorType", () => {
+ const nodeIds = new Set(["/do/0/call"]);
+
+ const errors: SdkError[] = [
+ vErr({
+ path: "/do/0/call",
+ }),
+ ];
+
+ expect(getNodeErrors(errors, "/do/0/call", nodeIds)).toHaveLength(1);
+ });
+
+ it("returns no node errors when there are no node ids", () => {
+ const errors: SdkError[] = [
+ vErr({
+ path: "/do/0/call",
+ message: "owned",
+ }),
+ ];
+
+ expect(getNodeErrors(errors, "/do/0/call", new Set())).toEqual([]);
+ });
+
+ it("returns no error node ids when there are no node ids", () => {
+ const errors: SdkError[] = [
+ vErr({
+ path: "/do/0/call",
+ message: "owned",
+ }),
+ ];
+
+ expect(getErrorNodeIds(errors, new Set())).toEqual(new Set());
+ });
+
+ it("treats all validation errors as general when there are no node ids", () => {
+ const errors: SdkError[] = [
+ vErr({
+ path: "/do/0/call",
+ message: "owned",
+ }),
+ ];
+
+ expect(getGeneralErrors(errors, new Set())).toEqual(errors);
+ });
+ });
});
diff --git a/packages/open-workflow-diagram-editor/tests/core/workflowSdk.integration.test.ts b/packages/open-workflow-diagram-editor/tests/core/workflowSdk.integration.test.ts
index 47b71da2..c6bff061 100644
--- a/packages/open-workflow-diagram-editor/tests/core/workflowSdk.integration.test.ts
+++ b/packages/open-workflow-diagram-editor/tests/core/workflowSdk.integration.test.ts
@@ -263,3 +263,81 @@ describe("buildFlatGraph", () => {
expect(() => buildFlatGraph(model!)).toThrow();
});
});
+
+describe("additional parseValidationErrorMessage edge cases", () => {
+ it.each(["#/required - ", " - missing property", " - "])(
+ "ignores malformed format-2 line: %s",
+ (message) => {
+ expect(parseValidationErrorMessage(message)).toEqual([]);
+ },
+ );
+
+ it.each([
+ "- | #/required | message | {}",
+ "- /path | | message | {}",
+ "- /path | #/required | | {}",
+ "- /path | #/required | message | ",
+ ])("ignores malformed format-1 line: %s", (message) => {
+ expect(parseValidationErrorMessage(message)).toEqual([]);
+ });
+
+ it("parses multiple validation errors from a single message", () => {
+ const message = `
+- /task1 | #/required | missing property | {}
+- /task2 | #/type | wrong type | {}
+#/document - missing required property 'document'
+`;
+
+ expect(parseValidationErrorMessage(message)).toEqual([
+ {
+ path: "/task1",
+ errorType: "#/required",
+ message: "missing property",
+ object: {},
+ },
+ {
+ path: "/task2",
+ errorType: "#/type",
+ message: "wrong type",
+ object: {},
+ },
+ {
+ errorType: "#/document",
+ message: "missing required property 'document'",
+ },
+ ]);
+ });
+
+ it("trims leading and trailing whitespace from lines", () => {
+ const message = `
+ - /task | #/required | missing property | {}
+ `;
+
+ expect(parseValidationErrorMessage(message)).toEqual([
+ {
+ path: "/task",
+ errorType: "#/required",
+ message: "missing property",
+ object: {},
+ },
+ ]);
+ });
+});
+
+describe("additional parseWorkflow edge cases", () => {
+ it("returns an error for whitespace-only input", () => {
+ const result = parseWorkflow(" \n\t ");
+
+ expect(result.model).toBeNull();
+ expect(result.errors).toHaveLength(1);
+ expect(result.errors[0]).toEqual(new Error("Not a valid workflow"));
+ });
+
+ it("returns an error when YAML evaluates to null", () => {
+ const result = parseWorkflow("null");
+
+ expect(result.model).toBeNull();
+ expect(result.errors).toHaveLength(1);
+ expect(result.errors[0]).toEqual(new Error("Not a valid workflow"));
+ });
+});
diff --git a/packages/open-workflow-diagram-editor/tests/diagram-editor/DiagramEditor.test.tsx b/packages/open-workflow-diagram-editor/tests/diagram-editor/DiagramEditor.test.tsx
index 17350dec..3c13851c 100644
--- a/packages/open-workflow-diagram-editor/tests/diagram-editor/DiagramEditor.test.tsx
+++ b/packages/open-workflow-diagram-editor/tests/diagram-editor/DiagramEditor.test.tsx
@@ -19,6 +19,7 @@ import { DiagramEditor } from "../../src/diagram-editor";
import { vi, expect, afterEach, describe, it } from "vitest";
import { BASIC_VALID_WORKFLOW_YAML } from "../fixtures/workflows";
import { t } from "../test-utils";
+import React from "react";
/* When js-yaml throws a YAMLException, parseWorkflow
returns a null model and the editor must fall back to the parsing error page. */
@@ -71,4 +72,55 @@ describe("DiagramEditor Component", () => {
expect(decRoot).not.toHaveClass("dark");
}
});
+
+ it("sets the lang attribute from the locale prop", () => {
+ render();
+
+ expect(screen.getByTestId("dec-root")).toHaveAttribute("lang", "fr");
+ });
+
+ it("updates the rendered content when the workflow content changes", () => {
+ const { rerender } = render(
+ ,
+ );
+
+ expect(screen.getByText(t("workflowError.parsing.title"))).toBeInTheDocument();
+
+ rerender();
+
+ expect(screen.getByTestId("diagram-container")).toBeInTheDocument();
+ expect(screen.queryByText(t("workflowError.parsing.title"))).not.toBeInTheDocument();
+ });
+
+ it("exposes the imperative ref API", () => {
+ const ref = React.createRef<{ doSomething: () => void }>();
+
+ render(
+ ,
+ );
+
+ expect(ref.current).not.toBeNull();
+ expect(ref.current?.doSomething).toBeTypeOf("function");
+
+ expect(() => ref.current?.doSomething()).not.toThrow();
+ });
+
+ it("renders the sidebar provider with the diagram", () => {
+ render();
+
+ expect(screen.getByTestId("diagram-container")).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: /toggle sidebar/i })).toBeInTheDocument();
+ });
+
+ it("resets the error boundary when the content changes", () => {
+ const { rerender } = render(
+ ,
+ );
+
+ expect(screen.getByText(t("workflowError.parsing.title"))).toBeInTheDocument();
+
+ rerender();
+
+ expect(screen.getByTestId("diagram-container")).toBeInTheDocument();
+ });
});
diff --git a/packages/open-workflow-diagram-editor/tests/diagram-editor/error-pages/DiagramEditorErrorBoundary.test.tsx b/packages/open-workflow-diagram-editor/tests/diagram-editor/error-pages/DiagramEditorErrorBoundary.test.tsx
index 3fa0debb..7098a916 100644
--- a/packages/open-workflow-diagram-editor/tests/diagram-editor/error-pages/DiagramEditorErrorBoundary.test.tsx
+++ b/packages/open-workflow-diagram-editor/tests/diagram-editor/error-pages/DiagramEditorErrorBoundary.test.tsx
@@ -16,7 +16,7 @@
import { render, screen } from "@testing-library/react";
import { DiagramEditorErrorBoundary } from "../../../src/diagram-editor/error-pages/DiagramEditorErrorBoundary";
-import { describe, expect, it } from "vitest";
+import { describe, expect, it, vi } from "vitest";
const ThrowError = ({ message = "Test error" }: { message?: string }) => {
throw new Error(message);
@@ -93,3 +93,73 @@ describe("DiagramEditorErrorBoundary", () => {
spy.mockRestore();
});
});
+
+const ThrowString = () => {
+ throw "boom";
+};
+
+describe("additional DiagramEditorErrorBoundary tests", () => {
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+
+ it("does not reset when resetKey does not change", () => {
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
+
+ const { rerender } = render(
+
+
+ ,
+ );
+
+ rerender(
+
+
+ ,
+ );
+
+ expect(screen.queryByText("Safe Content")).not.toBeInTheDocument();
+ expect(screen.getByText("Something went wrong")).toBeInTheDocument();
+
+ spy.mockRestore();
+ });
+
+ it("does not render an error snippet when a non-Error value is thrown", () => {
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
+
+ render(
+
+
+ ,
+ );
+
+ expect(screen.getByText("An unexpected error occurred")).toBeInTheDocument();
+
+ // The thrown value is not an Error, so no snippet should be displayed.
+ expect(screen.queryByText("boom")).not.toBeInTheDocument();
+
+ spy.mockRestore();
+ });
+
+ it("resets when resetKey changes from undefined to a value", () => {
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
+
+ const { rerender } = render(
+
+
+ ,
+ );
+
+ expect(screen.getByText("Something went wrong")).toBeInTheDocument();
+
+ rerender(
+
+
+ ,
+ );
+
+ expect(screen.getByText("Safe Content")).toBeInTheDocument();
+
+ spy.mockRestore();
+ });
+});
diff --git a/packages/open-workflow-diagram-editor/tests/side-panel/ErrorsSection.test.tsx b/packages/open-workflow-diagram-editor/tests/side-panel/ErrorsSection.test.tsx
index 0bfea6d7..5e118e2b 100644
--- a/packages/open-workflow-diagram-editor/tests/side-panel/ErrorsSection.test.tsx
+++ b/packages/open-workflow-diagram-editor/tests/side-panel/ErrorsSection.test.tsx
@@ -60,4 +60,33 @@ describe("ErrorSection", () => {
expect(container.querySelector(".dec-sidebar-error-field")).toBeNull();
});
+
+ it("renders field labels only for items that have a field", () => {
+ const items: ErrorItem[] = [
+ { field: "with", message: "missing endpoint" },
+ { message: "document-level error" },
+ ];
+
+ const { container } = renderWithProviders();
+
+ const fields = container.querySelectorAll(".dec-sidebar-error-field");
+
+ expect(fields).toHaveLength(1);
+ expect(fields[0]).toHaveTextContent("with");
+
+ expect(screen.getByText("missing endpoint")).toBeInTheDocument();
+ expect(screen.getByText("document-level error")).toBeInTheDocument();
+ });
+
+ it("renders the correct count for a single error", () => {
+ renderWithProviders();
+
+ expect(screen.getByText("1")).toBeInTheDocument();
+ });
+
+ it("renders the error list", () => {
+ const { container } = renderWithProviders();
+
+ expect(container.querySelector(".dec-sidebar-errors-list")).toBeInTheDocument();
+ });
});
diff --git a/packages/open-workflow-diagram-editor/tests/side-panel/SidePanel.test.tsx b/packages/open-workflow-diagram-editor/tests/side-panel/SidePanel.test.tsx
index 2f0da337..c550e154 100644
--- a/packages/open-workflow-diagram-editor/tests/side-panel/SidePanel.test.tsx
+++ b/packages/open-workflow-diagram-editor/tests/side-panel/SidePanel.test.tsx
@@ -71,4 +71,112 @@ describe("SidePanel", () => {
expect(screen.queryByText(/Copy Mermaid Code/i)).not.toBeInTheDocument();
expect(screen.queryByText(/Download as Mermaid File/i)).not.toBeInTheDocument();
});
+
+ it("renders node details when a node is selected", () => {
+ const { model } = parseWorkflow(WORKFLOW_WITH_METADATA_JSON);
+
+ const mockNode = {
+ id: "node-1",
+ type: "set",
+ position: { x: 0, y: 0 },
+ data: { label: "My Node" },
+ };
+
+ renderWithProviders(, {
+ model,
+ selectedNodeId: "node-1",
+ nodes: [mockNode],
+ });
+
+ expect(screen.getByText("My Node")).toBeInTheDocument();
+ expect(screen.queryByTestId("workflow-info")).not.toBeInTheDocument();
+ });
+
+ it("falls back to the translated node label when the selected node has no label", () => {
+ const { model } = parseWorkflow(WORKFLOW_WITH_METADATA_JSON);
+
+ const mockNode = {
+ id: "node-1",
+ type: "set",
+ position: { x: 0, y: 0 },
+ data: {},
+ };
+
+ renderWithProviders(, {
+ model,
+ selectedNodeId: "node-1",
+ nodes: [mockNode],
+ });
+
+ expect(screen.getByText("Node")).toBeInTheDocument();
+ });
+
+ it("renders workflow title when no node is selected", () => {
+ const { model } = parseWorkflow(WORKFLOW_WITH_METADATA_JSON);
+
+ renderWithProviders(, {
+ model,
+ selectedNodeId: null,
+ });
+
+ expect(screen.getByText("Workflow")).toBeInTheDocument();
+ });
+
+ it("renders the selection hint when no node is selected", () => {
+ const { model } = parseWorkflow(WORKFLOW_WITH_METADATA_JSON);
+
+ renderWithProviders(, {
+ model,
+ selectedNodeId: null,
+ });
+
+ expect(screen.getByText(/Select a node/i)).toBeInTheDocument();
+ });
+
+ it("renders sidebar with workflow aria-label when no node is selected", () => {
+ const { model } = parseWorkflow(WORKFLOW_WITH_METADATA_JSON);
+
+ renderWithProviders(, { model });
+
+ expect(
+ screen.getByRole("complementary", {
+ name: /workflow/i,
+ }),
+ ).toBeInTheDocument();
+ });
+
+ it("renders sidebar with node details aria-label when a node is selected", () => {
+ const { model } = parseWorkflow(WORKFLOW_WITH_METADATA_JSON);
+
+ const mockNode = {
+ id: "node-1",
+ type: "set",
+ position: { x: 0, y: 0 },
+ data: { label: "Node" },
+ };
+
+ renderWithProviders(, {
+ model,
+ selectedNodeId: "node-1",
+ nodes: [mockNode],
+ });
+
+ expect(
+ screen.getByRole("complementary", {
+ name: /node details/i,
+ }),
+ ).toBeInTheDocument();
+ });
+
+ it("does not render node details when selectedNodeId does not exist in nodes", () => {
+ const { model } = parseWorkflow(WORKFLOW_WITH_METADATA_JSON);
+
+ renderWithProviders(, {
+ model,
+ selectedNodeId: "missing-node",
+ nodes: [],
+ });
+
+ expect(screen.getByTestId("workflow-info")).toBeInTheDocument();
+ });
});
diff --git a/packages/open-workflow-diagram-editor/tests/side-panel/WorkflowInfoView.test.tsx b/packages/open-workflow-diagram-editor/tests/side-panel/WorkflowInfoView.test.tsx
index 5e7b2272..d2529966 100644
--- a/packages/open-workflow-diagram-editor/tests/side-panel/WorkflowInfoView.test.tsx
+++ b/packages/open-workflow-diagram-editor/tests/side-panel/WorkflowInfoView.test.tsx
@@ -105,5 +105,74 @@ describe("WorkflowInfoView", () => {
expect(screen.queryByTestId("sidebar-errors")).not.toBeInTheDocument();
});
+
+ it("renders only the title when summary is not provided", () => {
+ renderWithProviders(
+ ,
+ );
+
+ expect(screen.getByText("Metadata")).toBeInTheDocument();
+ expect(screen.getByText("Workflow Title")).toBeInTheDocument();
+ expect(screen.queryByText("Summary")).not.toBeInTheDocument();
+ });
+
+ it("renders only the summary when title is not provided", () => {
+ renderWithProviders(
+ ,
+ );
+
+ expect(screen.getByText("Metadata")).toBeInTheDocument();
+ expect(screen.getByText("Workflow summary")).toBeInTheDocument();
+ expect(screen.queryByText("Title")).not.toBeInTheDocument();
+ });
+
+ it("renders the metadata section when tags are the only metadata", () => {
+ renderWithProviders(
+ ,
+ );
+
+ expect(screen.getByText("Metadata")).toBeInTheDocument();
+ expect(screen.getByText("env: prod")).toBeInTheDocument();
+ });
+
+ it("does not render the tags field when tags is an empty object", () => {
+ renderWithProviders(
+ ,
+ );
+
+ expect(screen.queryByText("Tags")).not.toBeInTheDocument();
+ });
+
+ it("renders both general validation errors and raw errors together", () => {
+ renderWithProviders(, {
+ nodeIds: new Set(["/do/0/call"]),
+ errors: [{ path: "/document", message: "Missing version" }, new Error("Invalid workflow")],
+ });
+
+ expect(screen.getByText("Missing version")).toBeInTheDocument();
+ expect(screen.getByText("Invalid workflow")).toBeInTheDocument();
+ });
});
});