Merge branch 'abhi/builder-integration-test-part-1' into abhi/builder-integration-test-part-2

This commit is contained in:
Abhimanyu Yadav
2026-03-16 17:26:12 +05:30
committed by GitHub
6 changed files with 86 additions and 45 deletions

View File

@@ -5,13 +5,20 @@ import React from "react";
import { BlockUIType } from "../components/types";
import type { CustomNodeData } from "../components/FlowEditor/nodes/CustomNode/CustomNode";
import type { NodeProps } from "@xyflow/react";
import type { NodeExecutionResult } from "@/app/api/__generated__/models/nodeExecutionResult";
// ---- Mock sub-components ----
vi.mock(
"@/app/(platform)/build/components/FlowEditor/nodes/CustomNode/components/NodeContainer",
() => ({
NodeContainer: ({ children, hasErrors }: any) => (
NodeContainer: ({
children,
hasErrors,
}: {
children: React.ReactNode;
hasErrors: boolean;
}) => (
<div data-testid="node-container" data-has-errors={String(!!hasErrors)}>
{children}
</div>
@@ -22,7 +29,7 @@ vi.mock(
vi.mock(
"@/app/(platform)/build/components/FlowEditor/nodes/CustomNode/components/NodeHeader",
() => ({
NodeHeader: ({ data }: any) => (
NodeHeader: ({ data }: { data: CustomNodeData }) => (
<div data-testid="node-header">{data.title}</div>
),
}),
@@ -31,7 +38,7 @@ vi.mock(
vi.mock(
"@/app/(platform)/build/components/FlowEditor/nodes/CustomNode/components/StickyNoteBlock",
() => ({
StickyNoteBlock: ({ data }: any) => (
StickyNoteBlock: ({ data }: { data: CustomNodeData }) => (
<div data-testid="sticky-note-block">{data.title}</div>
),
}),
@@ -61,7 +68,7 @@ vi.mock(
vi.mock(
"@/app/(platform)/build/components/FlowEditor/nodes/CustomNode/components/NodeRightClickMenu",
() => ({
NodeRightClickMenu: ({ children }: any) => (
NodeRightClickMenu: ({ children }: { children: React.ReactNode }) => (
<div data-testid="node-right-click-menu">{children}</div>
),
}),
@@ -105,7 +112,7 @@ vi.mock(
vi.mock(
"@/components/renderers/InputRenderer/utils/input-schema-pre-processor",
() => ({
preprocessInputSchema: (schema: any) => schema,
preprocessInputSchema: (schema: unknown) => schema,
}),
);
@@ -133,7 +140,9 @@ vi.mock("@xyflow/react", async () => {
}),
useNodeId: () => "test-node-id",
useUpdateNodeInternals: () => vi.fn(),
Handle: ({ children }: any) => <div>{children}</div>,
Handle: ({ children }: { children: React.ReactNode }) => (
<div>{children}</div>
),
Position: { Left: "left", Right: "right", Top: "top", Bottom: "bottom" },
};
});
@@ -161,8 +170,8 @@ function buildNodeData(
function buildNodeProps(
dataOverrides: Partial<CustomNodeData> = {},
propsOverrides: Partial<NodeProps<any>> = {},
): any {
propsOverrides: Partial<NodeProps<CustomNodeData>> = {},
): NodeProps<CustomNodeData> {
return {
id: "node-1",
data: buildNodeData(dataOverrides),
@@ -183,12 +192,33 @@ function buildNodeProps(
function renderCustomNode(
dataOverrides: Partial<CustomNodeData> = {},
propsOverrides: Partial<NodeProps<any>> = {},
propsOverrides: Partial<NodeProps<CustomNodeData>> = {},
) {
const props = buildNodeProps(dataOverrides, propsOverrides);
return render(<CustomNode {...props} />);
}
function createExecutionResult(
overrides: Partial<NodeExecutionResult> = {},
): NodeExecutionResult {
return {
node_exec_id: overrides.node_exec_id ?? "exec-1",
node_id: overrides.node_id ?? "node-1",
graph_exec_id: overrides.graph_exec_id ?? "graph-exec-1",
graph_id: overrides.graph_id ?? "graph-1",
graph_version: overrides.graph_version ?? 1,
user_id: overrides.user_id ?? "test-user",
block_id: overrides.block_id ?? "block-1",
status: overrides.status ?? "COMPLETED",
input_data: overrides.input_data ?? {},
output_data: overrides.output_data ?? {},
add_time: overrides.add_time ?? new Date("2024-01-01T00:00:00Z"),
queue_time: overrides.queue_time ?? new Date("2024-01-01T00:00:00Z"),
start_time: overrides.start_time ?? new Date("2024-01-01T00:00:01Z"),
end_time: overrides.end_time ?? new Date("2024-01-01T00:00:02Z"),
};
}
// ---- Tests ----
beforeEach(() => {
@@ -333,9 +363,9 @@ describe("CustomNode", () => {
it("sets hasErrors when last execution result has error in output_data", () => {
renderCustomNode({
nodeExecutionResults: [
{
createExecutionResult({
output_data: { error: "Something went wrong" },
} as any,
}),
],
});
@@ -346,9 +376,9 @@ describe("CustomNode", () => {
it("does not set hasErrors when execution results have no error", () => {
renderCustomNode({
nodeExecutionResults: [
{
createExecutionResult({
output_data: { result: "success" },
} as any,
}),
],
});

View File

@@ -47,7 +47,6 @@ afterEach(() => {
});
beforeEach(() => {
cleanup();
useBlockMenuStore.getState().reset();
useBlockMenuStore.setState({
filters: [],

View File

@@ -149,7 +149,7 @@ describe("NewSaveControl", () => {
expect(screen.queryByTestId("save-control-version-output")).toBeNull();
});
it("enables save button when isSaving is false and disables when true", () => {
it("enables save button when isSaving is false", () => {
useControlPanelStore.setState({ saveControlOpen: true });
setupMock({ isSaving: false });
render(
@@ -162,6 +162,19 @@ describe("NewSaveControl", () => {
expect((saveButton as HTMLButtonElement).disabled).toBe(false);
});
it("disables save button when isSaving is true", () => {
useControlPanelStore.setState({ saveControlOpen: true });
setupMock({ isSaving: true });
render(
<TooltipProvider>
<NewSaveControl />
</TooltipProvider>,
);
const saveButton = screen.getByRole("button", { name: /save agent/i });
expect((saveButton as HTMLButtonElement).disabled).toBe(true);
});
it("calls handleSave on form submission with valid data", async () => {
useControlPanelStore.setState({ saveControlOpen: true });
const form = setupMock({ name: "My Agent", description: "A description" });

View File

@@ -42,7 +42,7 @@ function createTestEdge(
}
async function flushMicrotasks() {
await new Promise((resolve) => setTimeout(resolve, 0));
await new Promise((resolve) => queueMicrotask(resolve));
}
beforeEach(() => {

View File

@@ -7,9 +7,11 @@ import type { CustomNode } from "../components/FlowEditor/nodes/CustomNode/Custo
import type { CustomNodeData } from "../components/FlowEditor/nodes/CustomNode/CustomNode";
import type { NodeExecutionResult } from "@/app/api/__generated__/models/nodeExecutionResult";
function createTestNode(
overrides: Partial<CustomNode> & { id: string },
): CustomNode {
function createTestNode(overrides: {
id: string;
position?: { x: number; y: number };
data?: Partial<CustomNodeData>;
}): CustomNode {
const defaults: CustomNodeData = {
hardcodedValues: {},
title: "Test Block",
@@ -125,7 +127,7 @@ describe("nodeStore", () => {
const node1 = createTestNode({ id: "1" });
const node2 = createTestNode({
id: "2",
data: { title: "Node 2" } as CustomNodeData,
data: { title: "Node 2" },
});
useNodeStore.getState().setNodes([node1, node2]);
@@ -229,7 +231,7 @@ describe("nodeStore", () => {
id: "1",
data: {
metadata: { customized_name: "My Custom Name" },
} as Partial<CustomNodeData> as CustomNodeData,
},
});
const backend = useNodeStore
@@ -247,7 +249,7 @@ describe("nodeStore", () => {
id: "1",
data: {
metadata: { credentials_optional: true },
} as Partial<CustomNodeData> as CustomNodeData,
},
});
const backend = useNodeStore
@@ -262,7 +264,7 @@ describe("nodeStore", () => {
id: "1",
data: {
hardcodedValues: { filled: "value", empty: "" },
} as Partial<CustomNodeData> as CustomNodeData,
},
});
const backend = useNodeStore
@@ -530,7 +532,7 @@ describe("nodeStore", () => {
id: "1",
data: {
uiType: BlockUIType.INPUT,
} as Partial<CustomNodeData> as CustomNodeData,
},
}),
);
@@ -558,7 +560,7 @@ describe("nodeStore", () => {
id: "1",
data: {
uiType: BlockUIType.WEBHOOK,
} as Partial<CustomNodeData> as CustomNodeData,
},
}),
);
expect(useNodeStore.getState().hasWebhookNodes()).toBe(true);
@@ -570,7 +572,7 @@ describe("nodeStore", () => {
id: "1",
data: {
uiType: BlockUIType.WEBHOOK_MANUAL,
} as Partial<CustomNodeData> as CustomNodeData,
},
}),
);
expect(useNodeStore.getState().hasWebhookNodes()).toBe(true);
@@ -638,7 +640,7 @@ describe("nodeStore", () => {
id: "1",
data: {
hardcodedValues: { key: "value" },
} as Partial<CustomNodeData> as CustomNodeData,
},
}),
);
@@ -735,15 +737,11 @@ describe("nodeStore", () => {
useNodeStore.getState().addNodes([
createTestNode({
id: "1",
data: {
title: "First",
} as Partial<CustomNodeData> as CustomNodeData,
data: { title: "First" },
}),
createTestNode({
id: "1",
data: {
title: "Second",
} as Partial<CustomNodeData> as CustomNodeData,
data: { title: "Second" },
}),
]);
@@ -760,7 +758,7 @@ describe("nodeStore", () => {
data: {
title: "My Node",
hardcodedValues: { key: "val" },
} as Partial<CustomNodeData> as CustomNodeData,
},
}),
);

View File

@@ -413,12 +413,12 @@ describe("useCopyPaste", () => {
expect(mockReadText).toHaveBeenCalled();
});
// Wait a tick to ensure no state changes happen
await new Promise((r) => setTimeout(r, 50));
const { nodes } = useNodeStore.getState();
expect(nodes).toHaveLength(1);
expect(nodes[0].id).toBe("1");
// Ensure no state changes happen after clipboard read
await vi.waitFor(() => {
const { nodes } = useNodeStore.getState();
expect(nodes).toHaveLength(1);
expect(nodes[0].id).toBe("1");
});
});
it("does nothing when clipboard is empty", async () => {
@@ -437,11 +437,12 @@ describe("useCopyPaste", () => {
expect(mockReadText).toHaveBeenCalled();
});
await new Promise((r) => setTimeout(r, 50));
const { nodes } = useNodeStore.getState();
expect(nodes).toHaveLength(1);
expect(nodes[0].id).toBe("1");
// Ensure no state changes happen after clipboard read
await vi.waitFor(() => {
const { nodes } = useNodeStore.getState();
expect(nodes).toHaveLength(1);
expect(nodes[0].id).toBe("1");
});
});
});