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.
641 lines
21 KiB
641 lines
21 KiB
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import { render, screen, fireEvent } from "@testing-library/react";
|
|
import { Header } from "@/components/Header";
|
|
import { useLoginModalStore } from "@/store/loginModalStore";
|
|
import { useUserStore } from "@/store/userStore";
|
|
|
|
// Mock the workflow store
|
|
const mockSetWorkflowMetadata = vi.fn();
|
|
const mockSetWorkflowName = vi.fn();
|
|
const mockSaveToFile = vi.fn();
|
|
const mockLoadWorkflow = vi.fn();
|
|
const mockUseWorkflowStore = vi.fn();
|
|
const mockSetCurrentCanvasWorkflow = vi.fn();
|
|
|
|
vi.mock("@/store/workflowStore", () => ({
|
|
useWorkflowStore: (selector?: (state: unknown) => unknown) => {
|
|
if (selector) {
|
|
return mockUseWorkflowStore(selector);
|
|
}
|
|
return mockUseWorkflowStore((s: unknown) => s);
|
|
},
|
|
}));
|
|
|
|
vi.mock("@/store/canvasWorkflowStore", () => ({
|
|
useCanvasWorkflowStore: (selector?: (state: unknown) => unknown) => {
|
|
const state = {
|
|
currentCanvasWorkflowId: null,
|
|
currentCanvasWorkflowDescription: "",
|
|
setCurrentCanvasWorkflow: mockSetCurrentCanvasWorkflow,
|
|
};
|
|
return selector ? selector(state) : state;
|
|
},
|
|
}));
|
|
|
|
// Mock ProjectSetupModal
|
|
vi.mock("@/components/ProjectSetupModal", () => ({
|
|
ProjectSetupModal: ({ isOpen, mode }: { isOpen: boolean; mode: string }) => (
|
|
isOpen ? <div data-testid="project-setup-modal" data-mode={mode}>Project Setup Modal</div> : null
|
|
),
|
|
}));
|
|
|
|
// Mock WorkflowBrowserModal
|
|
vi.mock("@/components/WorkflowBrowserModal", () => ({
|
|
WorkflowBrowserModal: ({ isOpen }: { isOpen: boolean }) => (
|
|
isOpen ? <div data-testid="workflow-browser-modal">Workflow Browser</div> : null
|
|
),
|
|
}));
|
|
|
|
// Mock CostIndicator
|
|
vi.mock("@/components/CostIndicator", () => ({
|
|
CostIndicator: () => <div data-testid="cost-indicator">$0.00</div>,
|
|
}));
|
|
|
|
// Mock functions for comment navigation
|
|
const mockGetNodesWithComments = vi.fn();
|
|
const mockGetUnviewedCommentCount = vi.fn();
|
|
const mockMarkCommentViewed = vi.fn();
|
|
const mockSetNavigationTarget = vi.fn();
|
|
|
|
// Default store state factory
|
|
const createDefaultState = (overrides = {}) => ({
|
|
workflowName: "",
|
|
workflowId: "",
|
|
saveDirectoryPath: "",
|
|
hasUnsavedChanges: false,
|
|
lastSavedAt: null,
|
|
isSaving: false,
|
|
setWorkflowMetadata: mockSetWorkflowMetadata,
|
|
setWorkflowName: mockSetWorkflowName,
|
|
saveToFile: mockSaveToFile,
|
|
loadWorkflow: mockLoadWorkflow,
|
|
getNodesWithComments: mockGetNodesWithComments,
|
|
getUnviewedCommentCount: mockGetUnviewedCommentCount,
|
|
viewedCommentNodeIds: new Set<string>(),
|
|
markCommentViewed: mockMarkCommentViewed,
|
|
setNavigationTarget: mockSetNavigationTarget,
|
|
...overrides,
|
|
});
|
|
|
|
const createTestUserInfo = () => ({
|
|
id: 10561,
|
|
code: "u10561",
|
|
name: "Test User",
|
|
avatar: "",
|
|
gender: 0,
|
|
birthday: "",
|
|
phone: "",
|
|
email: "",
|
|
wechat: "",
|
|
signature: "",
|
|
country: "",
|
|
province: "",
|
|
city: "",
|
|
isMember: false,
|
|
memberLevel: 0,
|
|
memberCoins: 0,
|
|
otherCoins: 0,
|
|
pointPackageCoins: 0,
|
|
allCoins: 12,
|
|
isUsedCoinsRecently: false,
|
|
power: 0,
|
|
powerConsumed: 0,
|
|
powerRecharged: 0,
|
|
followingNum: 0,
|
|
fansNum: 0,
|
|
likeNum: 0,
|
|
postNum: 0,
|
|
taskNum: 0,
|
|
characterDesignGuide: false,
|
|
status: 1,
|
|
createTime: "",
|
|
updateTime: "",
|
|
});
|
|
|
|
describe("Header", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
// Default mock implementation - unconfigured project
|
|
mockGetNodesWithComments.mockReturnValue([]);
|
|
mockGetUnviewedCommentCount.mockReturnValue(0);
|
|
useLoginModalStore.setState({ isOpen: false, reason: "manual" });
|
|
useUserStore.setState({
|
|
token: null,
|
|
userInfo: null,
|
|
gatewayApiKey: null,
|
|
isLoadingUserInfo: false,
|
|
isLoadingGatewayApiKey: false,
|
|
userInfoError: null,
|
|
gatewayApiKeyError: null,
|
|
});
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState());
|
|
});
|
|
});
|
|
|
|
describe("Basic Rendering", () => {
|
|
it("should render the app title", () => {
|
|
render(<Header />);
|
|
expect(screen.getByText("Popi.TV")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should render the brand icon", () => {
|
|
const { container } = render(<Header />);
|
|
const icon = container.querySelector('img[src="/banana_icon.png"]');
|
|
expect(icon).toBeInTheDocument();
|
|
expect(icon).toHaveAttribute("src", "/banana_icon.png");
|
|
expect(icon).toHaveAttribute("alt", "");
|
|
expect(icon).toHaveAttribute("aria-hidden", "true");
|
|
});
|
|
|
|
it("should hide external author link", () => {
|
|
render(<Header />);
|
|
expect(screen.queryByText("Made by Willie")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should hide Discord support link", () => {
|
|
render(<Header />);
|
|
expect(screen.queryByTitle("Support")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should open the shared login modal when login is clicked", () => {
|
|
render(<Header />);
|
|
fireEvent.click(screen.getByRole("button", { name: "Login" }));
|
|
|
|
expect(useLoginModalStore.getState().isOpen).toBe(true);
|
|
expect(useLoginModalStore.getState().reason).toBe("manual");
|
|
});
|
|
|
|
it("should show user info when logged in", () => {
|
|
useUserStore.setState({
|
|
token: "login-token",
|
|
userInfo: createTestUserInfo(),
|
|
});
|
|
|
|
render(<Header />);
|
|
expect(screen.getByText("Test User")).toBeInTheDocument();
|
|
expect(screen.queryByRole("button", { name: "Login" })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should close the user menu when clicking outside", () => {
|
|
useUserStore.setState({
|
|
token: "login-token",
|
|
userInfo: createTestUserInfo(),
|
|
});
|
|
|
|
render(<Header />);
|
|
fireEvent.click(screen.getByTitle("Test User"));
|
|
expect(screen.getByText("ID 10561")).toBeInTheDocument();
|
|
|
|
fireEvent.mouseDown(document.body);
|
|
expect(screen.queryByText("ID 10561")).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe("Unconfigured Project State", () => {
|
|
it("should show 'Untitled' when no project name is set", () => {
|
|
render(<Header />);
|
|
expect(screen.getByText("Untitled")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should not show save status when project is not configured", () => {
|
|
render(<Header />);
|
|
expect(screen.queryByText("Not saved")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should show save button without unsaved indicator", () => {
|
|
const { container } = render(<Header />);
|
|
const redDot = container.querySelector(".bg-red-500.rounded-full");
|
|
expect(redDot).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should not render CostIndicator when project is not configured", () => {
|
|
render(<Header />);
|
|
expect(screen.queryByTestId("cost-indicator")).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe("Configured Project State", () => {
|
|
beforeEach(() => {
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState({
|
|
workflowName: "My Project",
|
|
workflowId: "project-123",
|
|
saveDirectoryPath: "/path/to/project",
|
|
}));
|
|
});
|
|
});
|
|
|
|
it("should show project name when configured", () => {
|
|
render(<Header />);
|
|
expect(screen.getByRole("textbox", { name: "Workflow name" })).toHaveValue("My Project");
|
|
});
|
|
|
|
it("should update project name on blur", () => {
|
|
render(<Header />);
|
|
const nameInput = screen.getByRole("textbox", { name: "Workflow name" });
|
|
|
|
fireEvent.change(nameInput, { target: { value: "Renamed Project" } });
|
|
fireEvent.blur(nameInput);
|
|
|
|
expect(mockSetWorkflowName).toHaveBeenCalledWith("Renamed Project");
|
|
});
|
|
|
|
it("should render CostIndicator when project is configured", () => {
|
|
render(<Header />);
|
|
expect(screen.getByTestId("cost-indicator")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should not show save status when no lastSavedAt timestamp", () => {
|
|
render(<Header />);
|
|
expect(screen.queryByText("Not saved")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should render Open Project Folder button when saveDirectoryPath is set", () => {
|
|
render(<Header />);
|
|
const folderButton = screen.getByTitle("Open Project Folder");
|
|
expect(folderButton).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe("Save State Display", () => {
|
|
it("should not show visible saving status when isSaving is true", () => {
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState({
|
|
workflowName: "My Project",
|
|
workflowId: "project-123",
|
|
saveDirectoryPath: "/path/to/project",
|
|
isSaving: true,
|
|
}));
|
|
});
|
|
|
|
render(<Header />);
|
|
expect(screen.queryByText("Saving...")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should not show formatted save time when lastSavedAt is set", () => {
|
|
const timestamp = new Date("2024-01-15T14:30:00").getTime();
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState({
|
|
workflowName: "My Project",
|
|
workflowId: "project-123",
|
|
saveDirectoryPath: "/path/to/project",
|
|
lastSavedAt: timestamp,
|
|
}));
|
|
});
|
|
|
|
render(<Header />);
|
|
expect(screen.queryByText(/Saved/)).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe("Unsaved Changes Indicator", () => {
|
|
it("should not show red dot when hasUnsavedChanges is true", () => {
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState({
|
|
workflowName: "My Project",
|
|
workflowId: "project-123",
|
|
saveDirectoryPath: "/path/to/project",
|
|
hasUnsavedChanges: true,
|
|
}));
|
|
});
|
|
|
|
const { container } = render(<Header />);
|
|
const redDot = container.querySelector(".bg-red-500.rounded-full");
|
|
expect(redDot).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should not show red dot when hasUnsavedChanges is false and project is configured", () => {
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState({
|
|
workflowName: "My Project",
|
|
workflowId: "project-123",
|
|
saveDirectoryPath: "/path/to/project",
|
|
hasUnsavedChanges: false,
|
|
}));
|
|
});
|
|
|
|
render(<Header />);
|
|
// Find the save button area and check there's no red dot inside it
|
|
const saveButton = screen.getByTitle("Save project");
|
|
const redDotInSaveButton = saveButton.querySelector(".bg-red-500.rounded-full");
|
|
expect(redDotInSaveButton).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should not show red dot when isSaving is true even if hasUnsavedChanges", () => {
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState({
|
|
workflowName: "My Project",
|
|
workflowId: "project-123",
|
|
saveDirectoryPath: "/path/to/project",
|
|
hasUnsavedChanges: true,
|
|
isSaving: true,
|
|
}));
|
|
});
|
|
|
|
render(<Header />);
|
|
const saveButton = screen.getByTitle("Saving...");
|
|
const redDotInSaveButton = saveButton.querySelector(".bg-red-500.rounded-full");
|
|
expect(redDotInSaveButton).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe("New Project Button", () => {
|
|
it("should open ProjectSetupModal in 'new' mode when save button clicked (unconfigured)", () => {
|
|
render(<Header />);
|
|
|
|
const saveButton = screen.getByTitle("Save project");
|
|
fireEvent.click(saveButton);
|
|
|
|
const modal = screen.getByTestId("project-setup-modal");
|
|
expect(modal).toBeInTheDocument();
|
|
expect(modal).toHaveAttribute("data-mode", "new");
|
|
});
|
|
});
|
|
|
|
describe("Open File Button", () => {
|
|
it("should render open file button when project is not configured", () => {
|
|
render(<Header />);
|
|
const openButton = screen.getByTitle("Open project");
|
|
expect(openButton).toBeInTheDocument();
|
|
});
|
|
|
|
it("should open WorkflowBrowserModal when open button is clicked", () => {
|
|
render(<Header />);
|
|
const openButton = screen.getByTitle("Open project");
|
|
fireEvent.click(openButton);
|
|
|
|
expect(screen.getByTestId("workflow-browser-modal")).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe("Save Button", () => {
|
|
beforeEach(() => {
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState({
|
|
workflowName: "My Project",
|
|
workflowId: "project-123",
|
|
saveDirectoryPath: "/path/to/project",
|
|
}));
|
|
});
|
|
});
|
|
|
|
it("should call saveToFile when clicked on configured project", () => {
|
|
render(<Header />);
|
|
const saveButton = screen.getByTitle("Save project");
|
|
fireEvent.click(saveButton);
|
|
|
|
expect(mockSaveToFile).toHaveBeenCalled();
|
|
});
|
|
|
|
it("should be disabled when isSaving is true", () => {
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState({
|
|
workflowName: "My Project",
|
|
workflowId: "project-123",
|
|
saveDirectoryPath: "/path/to/project",
|
|
isSaving: true,
|
|
}));
|
|
});
|
|
|
|
render(<Header />);
|
|
const saveButton = screen.getByTitle("Saving...");
|
|
expect(saveButton).toBeDisabled();
|
|
});
|
|
|
|
it("should open settings modal when project name is set but saveDirectoryPath is empty", () => {
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState({
|
|
workflowName: "My Project",
|
|
workflowId: "",
|
|
saveDirectoryPath: "",
|
|
}));
|
|
});
|
|
|
|
render(<Header />);
|
|
const saveButton = screen.getByTitle("Configure save location");
|
|
fireEvent.click(saveButton);
|
|
|
|
const modal = screen.getByTestId("project-setup-modal");
|
|
expect(modal).toBeInTheDocument();
|
|
expect(modal).toHaveAttribute("data-mode", "settings");
|
|
});
|
|
});
|
|
|
|
|
|
describe("Settings Button", () => {
|
|
it("should open ProjectSetupModal in 'settings' mode when settings button clicked", () => {
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState({
|
|
workflowName: "My Project",
|
|
workflowId: "project-123",
|
|
saveDirectoryPath: "/path/to/project",
|
|
}));
|
|
});
|
|
|
|
render(<Header />);
|
|
const settingsButton = screen.getByTitle("Project settings");
|
|
fireEvent.click(settingsButton);
|
|
|
|
const modal = screen.getByTestId("project-setup-modal");
|
|
expect(modal).toBeInTheDocument();
|
|
expect(modal).toHaveAttribute("data-mode", "settings");
|
|
});
|
|
|
|
it("should be visible for unconfigured projects", () => {
|
|
render(<Header />);
|
|
const settingsButton = screen.getByTitle("Project settings");
|
|
expect(settingsButton).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe("Open Project Folder Button", () => {
|
|
it("should not be visible when saveDirectoryPath is not set", () => {
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState({
|
|
workflowName: "My Project",
|
|
workflowId: "project-123",
|
|
saveDirectoryPath: "",
|
|
}));
|
|
});
|
|
|
|
render(<Header />);
|
|
expect(screen.queryByTitle("Open Project Folder")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should be visible when saveDirectoryPath is set", () => {
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState({
|
|
workflowName: "My Project",
|
|
workflowId: "project-123",
|
|
saveDirectoryPath: "/path/to/project",
|
|
}));
|
|
});
|
|
|
|
render(<Header />);
|
|
expect(screen.getByTitle("Open Project Folder")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should call fetch to open-directory API when clicked", async () => {
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState({
|
|
workflowName: "My Project",
|
|
workflowId: "project-123",
|
|
saveDirectoryPath: "/path/to/project",
|
|
}));
|
|
});
|
|
|
|
const mockFetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: () => Promise.resolve({ success: true }),
|
|
});
|
|
global.fetch = mockFetch;
|
|
|
|
render(<Header />);
|
|
const folderButton = screen.getByTitle("Open Project Folder");
|
|
fireEvent.click(folderButton);
|
|
|
|
expect(mockFetch).toHaveBeenCalledWith("/api/open-directory", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ path: "/path/to/project" }),
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("Comments Navigation Icon", () => {
|
|
it("should not render comments icon when no comments exist", () => {
|
|
mockGetNodesWithComments.mockReturnValue([]);
|
|
mockGetUnviewedCommentCount.mockReturnValue(0);
|
|
|
|
render(<Header />);
|
|
|
|
// No button with comment-related title should exist
|
|
expect(screen.queryByTitle(/unviewed comment/)).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should render comments icon with badge when comments exist", () => {
|
|
const mockNodes = [
|
|
{ id: "node-1", position: { x: 0, y: 0 }, type: "prompt", data: { comment: "Test" } },
|
|
{ id: "node-2", position: { x: 100, y: 0 }, type: "prompt", data: { comment: "Test 2" } },
|
|
];
|
|
mockGetNodesWithComments.mockReturnValue(mockNodes);
|
|
mockGetUnviewedCommentCount.mockReturnValue(2);
|
|
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState());
|
|
});
|
|
|
|
render(<Header />);
|
|
|
|
const commentsButton = screen.getByTitle(/2 unviewed comments/);
|
|
expect(commentsButton).toBeInTheDocument();
|
|
});
|
|
|
|
it("should show 9+ when unviewed count exceeds 9", () => {
|
|
const mockNodes = Array.from({ length: 10 }, (_, i) => ({
|
|
id: `node-${i}`,
|
|
position: { x: i * 100, y: 0 },
|
|
type: "prompt",
|
|
data: { comment: `Comment ${i}` },
|
|
}));
|
|
mockGetNodesWithComments.mockReturnValue(mockNodes);
|
|
mockGetUnviewedCommentCount.mockReturnValue(10);
|
|
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState());
|
|
});
|
|
|
|
render(<Header />);
|
|
|
|
// Badge should show 9+
|
|
expect(screen.getByText("9+")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should call setNavigationTarget when clicked", () => {
|
|
const mockNodes = [
|
|
{ id: "node-1", position: { x: 0, y: 0 }, type: "prompt", data: { comment: "Test" } },
|
|
];
|
|
mockGetNodesWithComments.mockReturnValue(mockNodes);
|
|
mockGetUnviewedCommentCount.mockReturnValue(1);
|
|
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState());
|
|
});
|
|
|
|
render(<Header />);
|
|
|
|
const commentsButton = screen.getByTitle(/1 unviewed comment/);
|
|
fireEvent.click(commentsButton);
|
|
|
|
expect(mockSetNavigationTarget).toHaveBeenCalledWith("node-1");
|
|
});
|
|
|
|
it("should call markCommentViewed when clicked", () => {
|
|
const mockNodes = [
|
|
{ id: "node-1", position: { x: 0, y: 0 }, type: "prompt", data: { comment: "Test" } },
|
|
];
|
|
mockGetNodesWithComments.mockReturnValue(mockNodes);
|
|
mockGetUnviewedCommentCount.mockReturnValue(1);
|
|
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState());
|
|
});
|
|
|
|
render(<Header />);
|
|
|
|
const commentsButton = screen.getByTitle(/1 unviewed comment/);
|
|
fireEvent.click(commentsButton);
|
|
|
|
expect(mockMarkCommentViewed).toHaveBeenCalledWith("node-1");
|
|
});
|
|
|
|
it("should navigate to first unviewed comment when clicked", () => {
|
|
const mockNodes = [
|
|
{ id: "node-1", position: { x: 0, y: 0 }, type: "prompt", data: { comment: "Test" } },
|
|
{ id: "node-2", position: { x: 100, y: 0 }, type: "prompt", data: { comment: "Test 2" } },
|
|
];
|
|
mockGetNodesWithComments.mockReturnValue(mockNodes);
|
|
mockGetUnviewedCommentCount.mockReturnValue(1);
|
|
|
|
// node-1 is already viewed
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState({
|
|
viewedCommentNodeIds: new Set(["node-1"]),
|
|
}));
|
|
});
|
|
|
|
render(<Header />);
|
|
|
|
const commentsButton = screen.getByTitle(/1 unviewed comment/);
|
|
fireEvent.click(commentsButton);
|
|
|
|
// Should navigate to node-2 (first unviewed)
|
|
expect(mockSetNavigationTarget).toHaveBeenCalledWith("node-2");
|
|
});
|
|
|
|
it("should navigate to first comment when all viewed", () => {
|
|
const mockNodes = [
|
|
{ id: "node-1", position: { x: 0, y: 0 }, type: "prompt", data: { comment: "Test" } },
|
|
{ id: "node-2", position: { x: 100, y: 0 }, type: "prompt", data: { comment: "Test 2" } },
|
|
];
|
|
mockGetNodesWithComments.mockReturnValue(mockNodes);
|
|
mockGetUnviewedCommentCount.mockReturnValue(0);
|
|
|
|
// All comments are viewed
|
|
mockUseWorkflowStore.mockImplementation((selector) => {
|
|
return selector(createDefaultState({
|
|
viewedCommentNodeIds: new Set(["node-1", "node-2"]),
|
|
}));
|
|
});
|
|
|
|
render(<Header />);
|
|
|
|
const commentsButton = screen.getByTitle(/0 unviewed comments/);
|
|
fireEvent.click(commentsButton);
|
|
|
|
// Should navigate to node-1 (first comment)
|
|
expect(mockSetNavigationTarget).toHaveBeenCalledWith("node-1");
|
|
});
|
|
});
|
|
});
|
|
|