feat(ui): colonne tags de versions à gauche du graph Git — #193
This commit is contained in:
@ -69,6 +69,8 @@ const LANE_W = 16;
|
|||||||
const ROW_H = 36;
|
const ROW_H = 36;
|
||||||
/** Radius of the commit dot. */
|
/** Radius of the commit dot. */
|
||||||
const DOT_R = 4.5;
|
const DOT_R = 4.5;
|
||||||
|
/** Stable version tag column, kept outside the DAG lanes. */
|
||||||
|
const TAGS_COL_W = 112;
|
||||||
|
|
||||||
/** Horizontal centre of a lane. */
|
/** Horizontal centre of a lane. */
|
||||||
const laneX = (lane: number) => lane * LANE_W + LANE_W / 2;
|
const laneX = (lane: number) => lane * LANE_W + LANE_W / 2;
|
||||||
@ -283,6 +285,8 @@ export function GitGraphView({ projectId }: GitGraphViewProps) {
|
|||||||
)}
|
)}
|
||||||
{vm.rows.map((row) => {
|
{vm.rows.map((row) => {
|
||||||
const isSelected = row.commit.hash === selectedHash;
|
const isSelected = row.commit.hash === selectedHash;
|
||||||
|
const tagRefs = row.commit.refs.filter(isTag);
|
||||||
|
const branchRefs = row.commit.refs.filter((ref) => !isTag(ref));
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={row.commit.hash}
|
key={row.commit.hash}
|
||||||
@ -297,21 +301,36 @@ export function GitGraphView({ projectId }: GitGraphViewProps) {
|
|||||||
)}
|
)}
|
||||||
style={{ height: ROW_H }}
|
style={{ height: ROW_H }}
|
||||||
>
|
>
|
||||||
|
{/* Version tags live in their own stable column, left of the DAG. */}
|
||||||
|
<div
|
||||||
|
data-testid={`git-tags-cell-${row.commit.hash.slice(0, 8)}`}
|
||||||
|
className="flex shrink-0 items-center gap-1 overflow-hidden"
|
||||||
|
style={{ width: TAGS_COL_W }}
|
||||||
|
>
|
||||||
|
{tagRefs.map((ref) => (
|
||||||
|
<span
|
||||||
|
key={ref}
|
||||||
|
title={ref}
|
||||||
|
className="min-w-0 max-w-full truncate rounded bg-warning/20 px-1 py-0.5 text-xs font-medium text-warning"
|
||||||
|
>
|
||||||
|
{ref}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* SVG lanes + node */}
|
{/* SVG lanes + node */}
|
||||||
<GraphColumn row={row} laneCount={laneCount} />
|
<GraphColumn row={row} laneCount={laneCount} />
|
||||||
|
|
||||||
{/* Ref badges */}
|
{/* Branch refs stay beside the graph in the existing refs zone. */}
|
||||||
{row.commit.refs.length > 0 && (
|
{branchRefs.length > 0 && (
|
||||||
<div className="flex shrink-0 flex-wrap gap-1">
|
<div
|
||||||
{row.commit.refs.map((ref) => (
|
data-testid={`git-branch-refs-cell-${row.commit.hash.slice(0, 8)}`}
|
||||||
|
className="flex shrink-0 flex-wrap gap-1"
|
||||||
|
>
|
||||||
|
{branchRefs.map((ref) => (
|
||||||
<span
|
<span
|
||||||
key={ref}
|
key={ref}
|
||||||
className={cn(
|
className="rounded bg-primary/20 px-1 py-0.5 text-xs font-medium text-primary"
|
||||||
"rounded px-1 py-0.5 text-xs font-medium",
|
|
||||||
isTag(ref)
|
|
||||||
? "bg-warning/20 text-warning"
|
|
||||||
: "bg-primary/20 text-primary",
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
{ref}
|
{ref}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
* - Clicking close hides the detail panel.
|
* - Clicking close hides the detail panel.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, it, expect } from "vitest";
|
import { describe, it, expect, vi } from "vitest";
|
||||||
import { render, screen, waitFor, fireEvent } from "@testing-library/react";
|
import { render, screen, waitFor, fireEvent } from "@testing-library/react";
|
||||||
|
|
||||||
import { MockGitGateway } from "@/adapters/mock";
|
import { MockGitGateway } from "@/adapters/mock";
|
||||||
@ -22,7 +22,7 @@ import { GitGraphView } from "./GitGraphView";
|
|||||||
// Helpers
|
// Helpers
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function renderView(git: MockGitGateway = new MockGitGateway()) {
|
function renderView(git: unknown = new MockGitGateway()) {
|
||||||
const gateways = { git } as unknown as Gateways;
|
const gateways = { git } as unknown as Gateways;
|
||||||
return render(
|
return render(
|
||||||
<DIProvider gateways={gateways}>
|
<DIProvider gateways={gateways}>
|
||||||
@ -71,6 +71,10 @@ describe("GitGraphView", () => {
|
|||||||
expect(tagBadge).toBeTruthy();
|
expect(tagBadge).toBeTruthy();
|
||||||
// Tag badge should have the warning colour class
|
// Tag badge should have the warning colour class
|
||||||
expect(tagBadge.className).toMatch(/warning/);
|
expect(tagBadge.className).toMatch(/warning/);
|
||||||
|
const tagCell = screen.getByTestId("git-tags-cell-aaaaaaaa");
|
||||||
|
expect(tagCell.textContent).toContain("tag: v1.0");
|
||||||
|
expect(tagCell.style.width).toBe("112px");
|
||||||
|
expect(tagBadge.className).toMatch(/truncate/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows 'feature' branch badge", async () => {
|
it("shows 'feature' branch badge", async () => {
|
||||||
@ -82,6 +86,29 @@ describe("GitGraphView", () => {
|
|||||||
expect(featureBadge.className).toMatch(/primary/);
|
expect(featureBadge.className).toMatch(/primary/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps tags in the left tag column and branches in the refs zone", async () => {
|
||||||
|
renderView({
|
||||||
|
graph: vi.fn(async () => [
|
||||||
|
{
|
||||||
|
hash: "1111111111111111111111111111111111111111",
|
||||||
|
summary: "Release build",
|
||||||
|
parents: [],
|
||||||
|
refs: ["main", "tag: v2026.08.14-long-release-name"],
|
||||||
|
author: "Alice",
|
||||||
|
timestamp: 1_717_200_000,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
await waitForCommits();
|
||||||
|
|
||||||
|
const tagCell = screen.getByTestId("git-tags-cell-11111111");
|
||||||
|
const branchCell = screen.getByTestId("git-branch-refs-cell-11111111");
|
||||||
|
expect(tagCell.textContent).toContain("tag: v2026.08.14-long-release-name");
|
||||||
|
expect(tagCell.textContent).not.toContain("main");
|
||||||
|
expect(branchCell.textContent).toContain("main");
|
||||||
|
expect(branchCell.textContent).not.toContain("tag: v2026.08.14-long-release-name");
|
||||||
|
});
|
||||||
|
|
||||||
it("clicking a commit row opens the detail panel", async () => {
|
it("clicking a commit row opens the detail panel", async () => {
|
||||||
renderView();
|
renderView();
|
||||||
await waitForCommits();
|
await waitForCommits();
|
||||||
|
|||||||
Reference in New Issue
Block a user