Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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(
<SidebarProvider open={false} onOpenChange={onOpenChange}>
<Sidebar>
<SidebarHeader>
<SidebarTrigger />
</SidebarHeader>
</Sidebar>
</SidebarProvider>,
);

await user.click(screen.getByRole("button", { name: /toggle sidebar/i }));

expect(onOpenChange).toHaveBeenCalledWith(true);
});

it("renders non-collapsible sidebar", () => {
const { container } = render(
<SidebarProvider>
<Sidebar collapsible="none">
<SidebarContent>Content</SidebarContent>
</Sidebar>
</SidebarProvider>,
);

const sidebar = findSidebar(container);

expect(sidebar).not.toHaveAttribute("data-state");
expect(sidebar).toBeInTheDocument();
});
});
28 changes: 28 additions & 0 deletions packages/open-workflow-diagram-editor/tests/core/graph.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
});
});
154 changes: 154 additions & 0 deletions packages/open-workflow-diagram-editor/tests/core/taskDetails.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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([]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
});
Loading