feat(web): show file statuses in review (#1683)
* feat(web): show file statuses in review * fix(web): address review status feedback * fix(web): deduplicate review sources * fix(web): align review progress identities * fix(web): guard review status labels * fix(web): enforce file status exhaustiveness * perf(web): index review progress sources * fix(web): preserve files during status hydration * fix(web): align multi-repo review progress
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
import { cleanup, render, screen } from "@testing-library/react";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { FileChangeStatus } from "@/lib/utils/file-change-status";
|
||||
import { FileStatusIcon } from "./file-status-icon";
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe("FileStatusIcon", () => {
|
||||
it.each<[FileChangeStatus, string, string]>([
|
||||
["added", "Added", "tabler-icon-plus"],
|
||||
["untracked", "Untracked", "tabler-icon-plus"],
|
||||
["modified", "Modified", "tabler-icon-circle-filled"],
|
||||
["deleted", "Deleted", "tabler-icon-minus"],
|
||||
["renamed", "Moved", "tabler-icon-arrow-right"],
|
||||
])("renders an accessible %s marker", (status, label, glyphClass) => {
|
||||
render(<FileStatusIcon status={status} />);
|
||||
|
||||
const marker = screen.getByRole("img", { name: label });
|
||||
expect(marker.getAttribute("data-file-status")).toBe(status);
|
||||
expect(marker.getAttribute("title")).toBe(label);
|
||||
expect(marker.querySelector(`.${glyphClass}`)).not.toBeNull();
|
||||
});
|
||||
|
||||
it("exposes previous-path context for a moved file", () => {
|
||||
render(<FileStatusIcon status="renamed" oldPath="src/old-name.ts" />);
|
||||
|
||||
const marker = screen.getByRole("img", { name: "Moved from src/old-name.ts" });
|
||||
expect(marker.getAttribute("title")).toBe("Moved from src/old-name.ts");
|
||||
});
|
||||
|
||||
it("merges caller classes into the fixed-size marker", () => {
|
||||
render(<FileStatusIcon status="modified" className="custom-marker" />);
|
||||
|
||||
const marker = screen.getByRole("img", { name: "Modified" });
|
||||
expect(marker.className).toContain("shrink-0");
|
||||
expect(marker.className).toContain("custom-marker");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
"use client";
|
||||
|
||||
import { IconArrowRight, IconCircleFilled, IconMinus, IconPlus } from "@tabler/icons-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { fileChangeStatusLabel, type FileChangeStatus } from "@/lib/utils/file-change-status";
|
||||
|
||||
type FileStatusIconProps = {
|
||||
status: FileChangeStatus;
|
||||
oldPath?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const markerClassName: Record<FileChangeStatus, string> = {
|
||||
added: "border-emerald-600 text-emerald-600",
|
||||
untracked: "border-emerald-600 text-emerald-600",
|
||||
modified: "border-amber-600 text-amber-600",
|
||||
deleted: "border-rose-600 text-rose-600",
|
||||
renamed: "border-purple-600 text-purple-600",
|
||||
};
|
||||
|
||||
function StatusGlyph({ status }: { status: FileChangeStatus }) {
|
||||
switch (status) {
|
||||
case "added":
|
||||
case "untracked":
|
||||
return <IconPlus aria-hidden="true" className="h-2 w-2" />;
|
||||
case "modified":
|
||||
return <IconCircleFilled aria-hidden="true" className="h-1 w-1" />;
|
||||
case "deleted":
|
||||
return <IconMinus aria-hidden="true" className="h-2 w-2" />;
|
||||
case "renamed":
|
||||
return <IconArrowRight aria-hidden="true" className="h-2 w-2" />;
|
||||
default: {
|
||||
const exhaustiveStatus: never = status;
|
||||
return exhaustiveStatus;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function FileStatusIcon({ status, oldPath, className }: FileStatusIconProps) {
|
||||
const label = fileChangeStatusLabel(status, oldPath);
|
||||
|
||||
return (
|
||||
<span
|
||||
role="img"
|
||||
aria-label={label}
|
||||
title={label}
|
||||
data-file-status={status}
|
||||
className={cn(
|
||||
"flex h-3 w-3 shrink-0 items-center justify-center rounded border",
|
||||
markerClassName[status],
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<StatusGlyph status={status} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user