You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

178 lines
5.0 KiB

import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { act } from "react";
import { ReactFlowProvider } from "@xyflow/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { SmartTextNode } from "@/components/nodes/SmartTextNode";
import { REQUEST_SMART_TEXT_EDIT_EVENT } from "@/utils/smartTextEditRequest";
const mockUpdateNodeData = vi.fn();
vi.mock("@/store/workflowStore", () => ({
useWorkflowStore: vi.fn((selector) => {
const state = {
updateNodeData: mockUpdateNodeData,
edges: [],
currentNodeIds: [],
groups: {},
nodes: [],
getConnectedInputs: vi.fn(() => ({ images: [], videos: [], text: null, dynamicInputs: {} })),
getNodesWithComments: vi.fn(() => []),
markCommentViewed: vi.fn(),
setNavigationTarget: vi.fn(),
setHoveredNodeId: vi.fn(),
};
return selector(state);
}),
}));
vi.mock("@/hooks/useSelectedNodeCount", () => ({
useSelectedNodeCount: () => 1,
}));
function TestWrapper({ children }: { children: React.ReactNode }) {
return (
<ReactFlowProvider>
{children}
</ReactFlowProvider>
);
}
const defaultProps = {
id: "text-1",
type: "smartText" as const,
data: {
manualLocked: true,
manualText: "First line\nLast character",
inputPrompt: "",
inputImages: [],
outputText: null,
provider: "openai" as const,
model: "gpt-4o-mini",
temperature: 0.7,
maxTokens: 1000,
status: "idle" as const,
error: null,
},
selected: true,
isConnectable: true,
positionAbsoluteX: 0,
positionAbsoluteY: 0,
zIndex: 0,
draggable: true,
dragging: false,
deletable: true,
selectable: true,
parentId: undefined,
dragHandle: undefined,
};
describe("SmartTextNode", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("enters editing when an external smart text edit request targets the node", async () => {
render(
<TestWrapper>
<SmartTextNode {...defaultProps} />
</TestWrapper>
);
const renderedText = document.querySelector("p");
expect(renderedText?.textContent).toContain("Last character");
act(() => {
window.dispatchEvent(new CustomEvent(REQUEST_SMART_TEXT_EDIT_EVENT, {
detail: { nodeId: "text-1" },
}));
});
const editor = await screen.findByRole("textbox", { name: /Write text content|编写文本内容|編寫文字內容|テキスト内容を書く/ });
await waitFor(() => expect(document.activeElement).toBe(editor));
const selection = window.getSelection();
expect(selection?.rangeCount).toBe(1);
expect(selection?.getRangeAt(0).collapsed).toBe(true);
});
it("prevents native text selection when double-clicking preview text", () => {
render(
<TestWrapper>
<SmartTextNode {...defaultProps} />
</TestWrapper>
);
const renderedText = document.querySelector("p");
expect(renderedText?.textContent).toContain("Last character");
const mouseDown = new MouseEvent("mousedown", {
bubbles: true,
cancelable: true,
detail: 2,
});
renderedText!.dispatchEvent(mouseDown);
expect(mouseDown.defaultPrevented).toBe(true);
});
it("ignores external smart text edit requests for other nodes", () => {
render(
<TestWrapper>
<SmartTextNode {...defaultProps} />
</TestWrapper>
);
act(() => {
window.dispatchEvent(new CustomEvent(REQUEST_SMART_TEXT_EDIT_EVENT, {
detail: { nodeId: "other-text-node" },
}));
});
expect(screen.queryByRole("textbox", { name: /Write text content|编写文本内容|編寫文字內容|テキスト内容を書く/ })).not.toBeInTheDocument();
});
it("keeps the editable surface isolated from canvas drag and pan interactions", async () => {
render(
<TestWrapper>
<SmartTextNode {...defaultProps} />
</TestWrapper>
);
act(() => {
window.dispatchEvent(new CustomEvent(REQUEST_SMART_TEXT_EDIT_EVENT, {
detail: { nodeId: "text-1" },
}));
});
const editor = await screen.findByRole("textbox", { name: /Write text content|编写文本内容|編寫文字內容|テキスト内容を書く/ });
expect(editor).toHaveClass("nodrag");
expect(editor).toHaveClass("nopan");
expect(editor).toHaveClass("nowheel");
});
it("commits edits on native blur", async () => {
render(
<TestWrapper>
<SmartTextNode {...defaultProps} />
</TestWrapper>
);
act(() => {
window.dispatchEvent(new CustomEvent(REQUEST_SMART_TEXT_EDIT_EVENT, {
detail: { nodeId: "text-1" },
}));
});
const editor = await screen.findByRole("textbox", { name: /Write text content|编写文本内容|編寫文字內容|テキスト内容を書く/ });
editor.innerHTML = "Updated text";
fireEvent.input(editor);
fireEvent.blur(editor);
await waitFor(() => {
expect(mockUpdateNodeData).toHaveBeenCalledWith("text-1", {
manualText: "Updated text",
});
});
});
});