From 038c23a2c8be7414d6e0e5b7afb6a7c11d8234b2 Mon Sep 17 00:00:00 2001 From: Jaw <53564781+jaw52@users.noreply.github.com> Date: Tue, 3 Nov 2026 14:40:18 +0800 Subject: [PATCH] feat(vcs): tint test files green in the changes tree (#940) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The changes tree in the Commit and Local Changes views now gives test files a light green background, so test changes stand out from production ones. A file counts as a test when it lies under a test source root (detected or marked by hand) or is named like a test (`*.test.ts`, `*Test.java`, …), the same check the project tree uses; folders under a test root get the tint too. Hover and selection still win. The colour is a new `--bg-file-tests` token derived from `--success` and `--bg-island`, so every theme gets a matching tint, and it follows test roots as they are marked or unmarked. Fixes #931. Co-authored-by: jaw52 <53564781+jaw52@users.noreply.github.com> --- src/features/vcs/CommitView.module.css | 4 ++ src/features/vcs/CommitView.tsx | 5 ++ .../vcs/__tests__/testFileColor.test.tsx | 65 +++++++++++++++++++ src/styles/tokens.css | 2 + 4 files changed, 76 insertions(+) create mode 100644 src/features/vcs/__tests__/testFileColor.test.tsx diff --git a/src/features/vcs/CommitView.module.css b/src/features/vcs/CommitView.module.css index bfe5eb2e0..6f69a9c53 100644 --- a/src/features/vcs/CommitView.module.css +++ b/src/features/vcs/CommitView.module.css @@ -218,6 +218,10 @@ gap: 4px; border-radius: var(--radius-small); } +/* Test files and the folders under a test root, as IDEA's green "Tests" file colour; hover and selection win. */ +.testFile:not(:hover):not([aria-selected="true"]) { + background: var(--bg-file-tests); +} .dropTarget { box-shadow: inset 0 0 0 1px var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); diff --git a/src/features/vcs/CommitView.tsx b/src/features/vcs/CommitView.tsx index b809529d6..cfb95a41e 100644 --- a/src/features/vcs/CommitView.tsx +++ b/src/features/vcs/CommitView.tsx @@ -8,6 +8,7 @@ import { tx } from "@/platform/i18n"; import clsx from "clsx"; import { executeAction } from "@/platform/actions"; import { dirname } from "@/platform/project"; +import { isTestFile, useEffectiveRoots } from "@/platform/projectRoots"; import { Button, Checkbox, @@ -398,6 +399,7 @@ export function ChangesTree({ mode }: { mode: "commit" | "local" | "dialog" }) { const viewRef = useRef(null); const repos = useVcs((s) => s.roots); + const projectRoots = useEffectiveRoots(); const roots = useMemo( () => buildChangeTree(status, root, groupByDir, lists, repos), [status, root, groupByDir, lists, repos], @@ -524,6 +526,9 @@ export function ChangesTree({ mode }: { mode: "commit" | "local" | "dialog" }) { getLabel={nodeText} getDirectory={(n) => (n.path && n.type !== "group" ? (n.type === "dir" ? n.path : dirname(n.path)) : null)} getChildren={(n) => n.children} + rowClassName={(n) => + n.type !== "group" && n.path && isTestFile(n.path, projectRoots) ? styles.testFile : undefined + } isExpandable={(n) => n.type !== "file"} expanded={expanded} onExpandedChange={(_next, node, open) => diff --git a/src/features/vcs/__tests__/testFileColor.test.tsx b/src/features/vcs/__tests__/testFileColor.test.tsx new file mode 100644 index 000000000..83d71f606 --- /dev/null +++ b/src/features/vcs/__tests__/testFileColor.test.tsx @@ -0,0 +1,65 @@ +import { act, render, screen } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +vi.mock("@/platform/ipc", async (importOriginal) => { + const real = await importOriginal(); + return { ...real, call: vi.fn() }; +}); + +vi.mock("../DiffEditor", () => ({ DiffEditor: () =>
})); + +import { ChangesTree } from "../CommitView"; +import { useVcs } from "../store"; +import { useProject } from "@/platform/project"; +import { useProjectRoots } from "@/platform/projectRoots"; + +const change = (relPath: string) => ({ + path: `/p/${relPath}`, + relPath, + staged: null, + unstaged: "modified" as const, + renamedFrom: null, +}); + +/** The tree row (`role=treeitem`) showing `name`. */ +const row = (name: string) => screen.getByText(name).closest('[role="treeitem"]') as HTMLElement; + +describe("test files in the changes tree (#931)", () => { + beforeEach(() => { + localStorage.clear(); + useProject.setState({ + project: { name: "p", root: "/p", trusted: true, isGit: true, settings: {} } as never, + settings: {} as never, + }); + useProjectRoots.setState({ + root: "/p", + detected: [{ path: "src/test/java", kind: "test", detectedFrom: "pom.xml" }], + }); + useVcs.setState({ + root: "/p", + repo: "/p", + roots: ["/p"], + status: [ + change("src/main/java/App.java"), + change("src/test/java/AppTest.java"), + change("web/button.test.ts"), + change("web/button.ts"), + ], + }); + }); + + it("tints files under a test root or named like a test, as IDEA's Tests scope does", () => { + render(); + expect(row("AppTest.java").className).toMatch(/testFile/); + expect(row("button.test.ts").className).toMatch(/testFile/); + expect(row("App.java").className).not.toMatch(/testFile/); + expect(row("button.ts").className).not.toMatch(/testFile/); + }); + + it("follows a folder being marked as test sources", () => { + render(); + expect(row("App.java").className).not.toMatch(/testFile/); + act(() => useProjectRoots.setState({ detected: [{ path: "src/main/java", kind: "test", detectedFrom: null }] })); + expect(row("App.java").className).toMatch(/testFile/); + }); +}); diff --git a/src/styles/tokens.css b/src/styles/tokens.css index ac12c4d87..6d27f3134 100644 --- a/src/styles/tokens.css +++ b/src/styles/tokens.css @@ -314,6 +314,8 @@ html { --bg-tree-selected: #2a4371; --bg-tree-selected-inactive: #33353b; --bg-tree-hover: #ffffff10; + /* IDEA's "Tests" file colour (FileColor.Green), derived so every theme gets a matching tint. */ + --bg-file-tests: color-mix(in srgb, var(--success) 14%, var(--bg-island)); --bg-list-selected: var(--bg-tree-selected); --bg-toolbar-hover: #ffffff17; --bg-toolbar-pressed: #ffffff29;