fix(views): show full repo URLs in project creation (#2045)

This commit is contained in:
Manu
2026-05-04 14:50:17 +02:00
committed by GitHub
parent 1ff4e27e77
commit fee393df1f
2 changed files with 196 additions and 2 deletions

View 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();
});
});

View File

@@ -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)}