mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-05 13:29:44 +02:00
fix(views): show full repo URLs in project creation (#2045)
This commit is contained in:
168
packages/views/modals/create-project.test.tsx
Normal file
168
packages/views/modals/create-project.test.tsx
Normal file
@@ -0,0 +1,168 @@
|
||||
import React from "react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
|
||||
const longRepoUrl =
|
||||
"https://github.com/multica-ai/a-very-long-repository-name-that-needs-a-tooltip";
|
||||
|
||||
vi.mock("@tanstack/react-query", () => ({
|
||||
useQuery: () => ({ data: [] }),
|
||||
}));
|
||||
|
||||
vi.mock("@multica/core/projects/mutations", () => ({
|
||||
useCreateProject: () => ({ mutateAsync: vi.fn() }),
|
||||
}));
|
||||
|
||||
vi.mock("@multica/core/projects", () => ({
|
||||
useProjectDraftStore: (selector: (state: unknown) => unknown) =>
|
||||
selector({
|
||||
draft: {
|
||||
title: "",
|
||||
description: "",
|
||||
status: "planned",
|
||||
priority: "medium",
|
||||
leadType: undefined,
|
||||
leadId: undefined,
|
||||
icon: undefined,
|
||||
},
|
||||
setDraft: vi.fn(),
|
||||
clearDraft: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("@multica/core/hooks", () => ({
|
||||
useWorkspaceId: () => "workspace-1",
|
||||
}));
|
||||
|
||||
vi.mock("@multica/core/paths", () => ({
|
||||
useCurrentWorkspace: () => ({
|
||||
id: "workspace-1",
|
||||
name: "Test Workspace",
|
||||
slug: "test-workspace",
|
||||
repos: [{ url: longRepoUrl }],
|
||||
}),
|
||||
useWorkspacePaths: () => ({
|
||||
projectDetail: (id: string) => `/test-workspace/projects/${id}`,
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("@multica/core/workspace/queries", () => ({
|
||||
memberListOptions: () => ({ queryKey: ["members"], queryFn: vi.fn() }),
|
||||
agentListOptions: () => ({ queryKey: ["agents"], queryFn: vi.fn() }),
|
||||
}));
|
||||
|
||||
vi.mock("@multica/core/workspace/hooks", () => ({
|
||||
useActorName: () => ({ getActorName: vi.fn() }),
|
||||
}));
|
||||
|
||||
vi.mock("../navigation", () => ({
|
||||
useNavigation: () => ({ push: vi.fn() }),
|
||||
}));
|
||||
|
||||
vi.mock("../editor", () => {
|
||||
const ContentEditor = React.forwardRef<HTMLTextAreaElement, { placeholder?: string }>(
|
||||
({ placeholder }, ref) => <textarea ref={ref} placeholder={placeholder} />,
|
||||
);
|
||||
ContentEditor.displayName = "ContentEditor";
|
||||
|
||||
return {
|
||||
ContentEditor,
|
||||
TitleEditor: ({
|
||||
placeholder,
|
||||
onChange,
|
||||
}: {
|
||||
placeholder?: string;
|
||||
onChange?: (value: string) => void;
|
||||
}) => <input placeholder={placeholder} onChange={(e) => onChange?.(e.target.value)} />,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../issues/components/priority-icon", () => ({
|
||||
PriorityIcon: () => <span data-testid="priority-icon" />,
|
||||
}));
|
||||
|
||||
vi.mock("../common/actor-avatar", () => ({
|
||||
ActorAvatar: () => <span data-testid="actor-avatar" />,
|
||||
}));
|
||||
|
||||
vi.mock("@multica/ui/components/ui/dialog", () => ({
|
||||
Dialog: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
DialogContent: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
DialogTitle: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock("@multica/ui/components/ui/dropdown-menu", () => ({
|
||||
DropdownMenu: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
DropdownMenuTrigger: ({ render }: { render: React.ReactNode }) => <>{render}</>,
|
||||
DropdownMenuContent: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
DropdownMenuItem: ({
|
||||
children,
|
||||
onClick,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
}) => (
|
||||
<button type="button" onClick={onClick}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock("@multica/ui/components/ui/popover", () => ({
|
||||
Popover: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
PopoverTrigger: ({ render }: { render: React.ReactNode }) => <>{render}</>,
|
||||
PopoverContent: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock("@multica/ui/components/ui/tooltip", () => ({
|
||||
Tooltip: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
TooltipTrigger: ({ render }: { render: React.ReactNode }) => <>{render}</>,
|
||||
TooltipContent: ({ children }: { children: React.ReactNode }) => (
|
||||
<div role="tooltip">{children}</div>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock("@multica/ui/components/ui/button", () => ({
|
||||
Button: ({
|
||||
children,
|
||||
disabled,
|
||||
onClick,
|
||||
type = "button",
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
onClick?: () => void;
|
||||
type?: "button" | "submit" | "reset";
|
||||
}) => (
|
||||
<button type={type} disabled={disabled} onClick={onClick}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock("@multica/ui/components/common/emoji-picker", () => ({
|
||||
EmojiPicker: () => null,
|
||||
}));
|
||||
|
||||
vi.mock("@multica/ui/lib/utils", () => ({
|
||||
cn: (...values: Array<string | false | null | undefined>) =>
|
||||
values.filter(Boolean).join(" "),
|
||||
}));
|
||||
|
||||
vi.mock("sonner", () => ({
|
||||
toast: {
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
import { CreateProjectModal } from "./create-project";
|
||||
|
||||
describe("CreateProjectModal", () => {
|
||||
it("exposes full repository URLs in the repository picker", () => {
|
||||
render(<CreateProjectModal onClose={vi.fn()} />);
|
||||
|
||||
expect(screen.getByTitle(longRepoUrl)).toHaveTextContent(longRepoUrl);
|
||||
expect(screen.getByRole("tooltip", { name: longRepoUrl })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -73,6 +73,32 @@ function PillButton({
|
||||
);
|
||||
}
|
||||
|
||||
function RepoUrlText({
|
||||
url,
|
||||
className,
|
||||
}: {
|
||||
url: string;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<span
|
||||
title={url}
|
||||
className={cn("truncate flex-1 text-left", className)}
|
||||
>
|
||||
{url}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<TooltipContent side="top" align="start" className="max-w-sm break-all">
|
||||
{url}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
export function CreateProjectModal({ onClose }: { onClose: () => void }) {
|
||||
const router = useNavigation();
|
||||
const workspace = useCurrentWorkspace();
|
||||
@@ -446,7 +472,7 @@ export function CreateProjectModal({ onClose }: { onClose: () => void }) {
|
||||
className="size-3.5"
|
||||
/>
|
||||
<GithubIcon className="size-3.5" />
|
||||
<span className="truncate flex-1 text-left">{repo.url}</span>
|
||||
<RepoUrlText url={repo.url} />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
@@ -492,7 +518,7 @@ export function CreateProjectModal({ onClose }: { onClose: () => void }) {
|
||||
className="flex items-center gap-2 text-xs"
|
||||
>
|
||||
<GithubIcon className="size-3 text-muted-foreground" />
|
||||
<span className="truncate flex-1">{url}</span>
|
||||
<RepoUrlText url={url} />
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleRepo(url)}
|
||||
|
||||
Reference in New Issue
Block a user