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.
280 lines
8.8 KiB
280 lines
8.8 KiB
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
import { renderHook, act } from "@testing-library/react";
|
|
import { useCommentNavigation } from "../useCommentNavigation";
|
|
import { useWorkflowStore } from "@/store/workflowStore";
|
|
import type { WorkflowNode } from "@/types";
|
|
|
|
// Mock the Toast hook
|
|
vi.mock("@/components/Toast", () => ({
|
|
useToast: {
|
|
getState: () => ({
|
|
show: vi.fn(),
|
|
}),
|
|
},
|
|
}));
|
|
|
|
// Mock the logger
|
|
vi.mock("@/utils/logger", () => ({
|
|
logger: {
|
|
info: vi.fn(),
|
|
warn: vi.fn(),
|
|
error: vi.fn(),
|
|
startSession: vi.fn().mockResolvedValue(undefined),
|
|
endSession: vi.fn().mockResolvedValue(undefined),
|
|
getCurrentSession: vi.fn().mockReturnValue(null),
|
|
},
|
|
}));
|
|
|
|
// Mock localStorage
|
|
const mockLocalStorage: Record<string, string> = {};
|
|
vi.stubGlobal("localStorage", {
|
|
getItem: vi.fn((key: string) => mockLocalStorage[key] || null),
|
|
setItem: vi.fn((key: string, value: string) => {
|
|
mockLocalStorage[key] = value;
|
|
}),
|
|
removeItem: vi.fn((key: string) => {
|
|
delete mockLocalStorage[key];
|
|
}),
|
|
clear: vi.fn(() => {
|
|
Object.keys(mockLocalStorage).forEach((key) => delete mockLocalStorage[key]);
|
|
}),
|
|
});
|
|
|
|
// Helper to reset store state between tests
|
|
function resetStore() {
|
|
const store = useWorkflowStore.getState();
|
|
store.clearWorkflow();
|
|
}
|
|
|
|
// Helper to create a test node
|
|
function createTestNode(
|
|
id: string,
|
|
type: string,
|
|
data: Record<string, unknown> = {},
|
|
position = { x: 0, y: 0 }
|
|
): WorkflowNode {
|
|
return {
|
|
id,
|
|
type: type as WorkflowNode["type"],
|
|
position,
|
|
data: data as WorkflowNode["data"],
|
|
};
|
|
}
|
|
|
|
describe("useCommentNavigation", () => {
|
|
beforeEach(() => {
|
|
resetStore();
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
afterEach(() => {
|
|
resetStore();
|
|
});
|
|
|
|
describe("when node has no comment", () => {
|
|
it("should return null", () => {
|
|
useWorkflowStore.setState({
|
|
nodes: [createTestNode("node-1", "prompt", { prompt: "test" })],
|
|
});
|
|
|
|
const { result } = renderHook(() => useCommentNavigation("node-1"));
|
|
|
|
expect(result.current).toBeNull();
|
|
});
|
|
|
|
it("should return null for empty comment", () => {
|
|
useWorkflowStore.setState({
|
|
nodes: [createTestNode("node-1", "prompt", { prompt: "test", comment: "" })],
|
|
});
|
|
|
|
const { result } = renderHook(() => useCommentNavigation("node-1"));
|
|
|
|
expect(result.current).toBeNull();
|
|
});
|
|
|
|
it("should return null for whitespace-only comment", () => {
|
|
useWorkflowStore.setState({
|
|
nodes: [createTestNode("node-1", "prompt", { prompt: "test", comment: " " })],
|
|
});
|
|
|
|
const { result } = renderHook(() => useCommentNavigation("node-1"));
|
|
|
|
expect(result.current).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("when node has a comment", () => {
|
|
it("should return navigation props with correct index and count", () => {
|
|
useWorkflowStore.setState({
|
|
nodes: [
|
|
createTestNode("node-1", "prompt", { prompt: "test", comment: "First comment" }, { x: 0, y: 0 }),
|
|
createTestNode("node-2", "prompt", { prompt: "test", comment: "Second comment" }, { x: 100, y: 0 }),
|
|
createTestNode("node-3", "prompt", { prompt: "test" }, { x: 200, y: 0 }), // No comment
|
|
],
|
|
});
|
|
|
|
const { result } = renderHook(() => useCommentNavigation("node-1"));
|
|
|
|
expect(result.current).not.toBeNull();
|
|
expect(result.current?.currentIndex).toBe(1); // 1-based index
|
|
expect(result.current?.totalCount).toBe(2);
|
|
});
|
|
|
|
it("should return correct index for second node with comment", () => {
|
|
useWorkflowStore.setState({
|
|
nodes: [
|
|
createTestNode("node-1", "prompt", { prompt: "test", comment: "First comment" }, { x: 0, y: 0 }),
|
|
createTestNode("node-2", "prompt", { prompt: "test", comment: "Second comment" }, { x: 100, y: 0 }),
|
|
],
|
|
});
|
|
|
|
const { result } = renderHook(() => useCommentNavigation("node-2"));
|
|
|
|
expect(result.current?.currentIndex).toBe(2);
|
|
expect(result.current?.totalCount).toBe(2);
|
|
});
|
|
});
|
|
|
|
describe("onPrevious navigation", () => {
|
|
it("should navigate to previous comment node", () => {
|
|
useWorkflowStore.setState({
|
|
nodes: [
|
|
createTestNode("node-1", "prompt", { prompt: "test", comment: "First" }, { x: 0, y: 0 }),
|
|
createTestNode("node-2", "prompt", { prompt: "test", comment: "Second" }, { x: 100, y: 0 }),
|
|
],
|
|
});
|
|
|
|
const { result } = renderHook(() => useCommentNavigation("node-2"));
|
|
|
|
act(() => {
|
|
result.current?.onPrevious();
|
|
});
|
|
|
|
const store = useWorkflowStore.getState();
|
|
expect(store.navigationTarget?.nodeId).toBe("node-1");
|
|
});
|
|
|
|
it("should wrap from first to last when navigating previous", () => {
|
|
useWorkflowStore.setState({
|
|
nodes: [
|
|
createTestNode("node-1", "prompt", { prompt: "test", comment: "First" }, { x: 0, y: 0 }),
|
|
createTestNode("node-2", "prompt", { prompt: "test", comment: "Second" }, { x: 100, y: 0 }),
|
|
],
|
|
});
|
|
|
|
const { result } = renderHook(() => useCommentNavigation("node-1"));
|
|
|
|
act(() => {
|
|
result.current?.onPrevious();
|
|
});
|
|
|
|
const store = useWorkflowStore.getState();
|
|
expect(store.navigationTarget?.nodeId).toBe("node-2");
|
|
});
|
|
|
|
it("should mark target comment as viewed", () => {
|
|
useWorkflowStore.setState({
|
|
nodes: [
|
|
createTestNode("node-1", "prompt", { prompt: "test", comment: "First" }, { x: 0, y: 0 }),
|
|
createTestNode("node-2", "prompt", { prompt: "test", comment: "Second" }, { x: 100, y: 0 }),
|
|
],
|
|
});
|
|
|
|
const { result } = renderHook(() => useCommentNavigation("node-2"));
|
|
|
|
act(() => {
|
|
result.current?.onPrevious();
|
|
});
|
|
|
|
const store = useWorkflowStore.getState();
|
|
expect(store.viewedCommentNodeIds.has("node-1")).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("onNext navigation", () => {
|
|
it("should navigate to next comment node", () => {
|
|
useWorkflowStore.setState({
|
|
nodes: [
|
|
createTestNode("node-1", "prompt", { prompt: "test", comment: "First" }, { x: 0, y: 0 }),
|
|
createTestNode("node-2", "prompt", { prompt: "test", comment: "Second" }, { x: 100, y: 0 }),
|
|
],
|
|
});
|
|
|
|
const { result } = renderHook(() => useCommentNavigation("node-1"));
|
|
|
|
act(() => {
|
|
result.current?.onNext();
|
|
});
|
|
|
|
const store = useWorkflowStore.getState();
|
|
expect(store.navigationTarget?.nodeId).toBe("node-2");
|
|
});
|
|
|
|
it("should wrap from last to first when navigating next", () => {
|
|
useWorkflowStore.setState({
|
|
nodes: [
|
|
createTestNode("node-1", "prompt", { prompt: "test", comment: "First" }, { x: 0, y: 0 }),
|
|
createTestNode("node-2", "prompt", { prompt: "test", comment: "Second" }, { x: 100, y: 0 }),
|
|
],
|
|
});
|
|
|
|
const { result } = renderHook(() => useCommentNavigation("node-2"));
|
|
|
|
act(() => {
|
|
result.current?.onNext();
|
|
});
|
|
|
|
const store = useWorkflowStore.getState();
|
|
expect(store.navigationTarget?.nodeId).toBe("node-1");
|
|
});
|
|
|
|
it("should mark target comment as viewed", () => {
|
|
useWorkflowStore.setState({
|
|
nodes: [
|
|
createTestNode("node-1", "prompt", { prompt: "test", comment: "First" }, { x: 0, y: 0 }),
|
|
createTestNode("node-2", "prompt", { prompt: "test", comment: "Second" }, { x: 100, y: 0 }),
|
|
],
|
|
});
|
|
|
|
const { result } = renderHook(() => useCommentNavigation("node-1"));
|
|
|
|
act(() => {
|
|
result.current?.onNext();
|
|
});
|
|
|
|
const store = useWorkflowStore.getState();
|
|
expect(store.viewedCommentNodeIds.has("node-2")).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("comment sorting by position", () => {
|
|
it("should sort by Y position (top to bottom)", () => {
|
|
useWorkflowStore.setState({
|
|
nodes: [
|
|
createTestNode("node-bottom", "prompt", { prompt: "test", comment: "Bottom" }, { x: 0, y: 200 }),
|
|
createTestNode("node-top", "prompt", { prompt: "test", comment: "Top" }, { x: 0, y: 0 }),
|
|
],
|
|
});
|
|
|
|
const { result } = renderHook(() => useCommentNavigation("node-top"));
|
|
|
|
expect(result.current?.currentIndex).toBe(1); // Top should be first
|
|
expect(result.current?.totalCount).toBe(2);
|
|
});
|
|
|
|
it("should sort by X position within same row (left to right)", () => {
|
|
// Within 50px Y threshold, should sort by X
|
|
useWorkflowStore.setState({
|
|
nodes: [
|
|
createTestNode("node-right", "prompt", { prompt: "test", comment: "Right" }, { x: 200, y: 10 }),
|
|
createTestNode("node-left", "prompt", { prompt: "test", comment: "Left" }, { x: 0, y: 0 }),
|
|
],
|
|
});
|
|
|
|
const { result } = renderHook(() => useCommentNavigation("node-left"));
|
|
|
|
expect(result.current?.currentIndex).toBe(1); // Left should be first
|
|
expect(result.current?.totalCount).toBe(2);
|
|
});
|
|
});
|
|
});
|
|
|