Файли
openidea/src/features/project-tree/moduleGroups.test.ts
T
yoo hyeong chan 0dfdbbc323 feat(gradle): link and unlink independent Gradle builds (#1413)
Opening a folder of independent Gradle builds selected only the first build. Link Gradle Projects… (pick a build's settings or build file) and Unlink on a build's root let an ordinary project window manage several independent builds without changing their scripts. The build model holds a list of Gradle builds; each keeps its own wrapper, working directory, environment, task routing and sync cache, keyed by the owning build directory. The links persist in the project's Gradle settings (linkedProjects); unlinking leaves files and saved run configurations alone. Existing includeBuild support is unchanged, and an explicitly linked build takes precedence over a composite that also includes it. Workspace windows keep Link/Unlink disabled.

Co-authored-by: gudcks0305 <[email protected]>
2026-11-05 11:23:39 +08:00

138 рядки
4.8 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { FileEntry } from "@/bindings";
import { buildModel, DEFAULT_TREE_OPTIONS, type ExtraRoot, withExtraRoots } from "./model";
import { gradleModuleGroups } from "./moduleGroups";
type Shown = { label: string; detail?: string; children?: Shown[] };
const shape = (e: ExtraRoot): Shown => ({
label: e.label,
...(e.detail ? { detail: e.detail } : {}),
...(e.children ? { children: e.children.map(shape) } : {}),
});
describe("gradleModuleGroups (IDEA's module groups)", () => {
// `includeFlat` sibling modules, one with layer modules nested in its folder.
const flat = [
{ path: ":", dir: "" },
{ path: ":common", dir: "/w/common" },
{ path: ":billing", dir: "/w/billing" },
...["core", "api"].map((l) => ({ path: `:billing:${l}`, dir: `/w/billing/modules/${l}` })),
];
it("lists every module of the group, nested ones flattened, as IntelliJ shows it", () => {
expect(gradleModuleGroups(flat, "Shop", "/w/shop").map(shape)).toEqual([
{
label: "Shop",
children: [
{ label: "api", detail: "Shop.billing.api" },
{ label: "billing", detail: "Shop.billing" },
{ label: "common", detail: "Shop.common" },
{ label: "core", detail: "Shop.billing.core" },
],
},
]);
});
it("shows no group while every module lives in the project folder", () => {
const inside = [
{ path: ":", dir: "" },
{ path: ":app", dir: "app" },
{ path: ":libs:core", dir: "libs/core" },
];
expect(gradleModuleGroups(inside, "demo", "/w/demo")).toEqual([]);
});
it("keeps a subgroup that no module is named like", () => {
const projects = [
{ path: ":", dir: "" },
{ path: ":svc", dir: "/w/svc" },
{ path: ":libs:a", dir: "/w/libs/a" },
{ path: ":libs:b", dir: "/w/libs/b" },
];
expect(gradleModuleGroups(projects, "R", "/w/r").map(shape)).toEqual([
{
label: "R",
children: [
{
label: "libs",
children: [
{ label: "a", detail: "R.libs.a" },
{ label: "b", detail: "R.libs.b" },
],
},
{ label: "svc", detail: "R.svc" },
],
},
]);
});
it("names an included build's modules after it and lists it as an ungrouped module", () => {
const projects = [
{ path: ":", dir: "" },
{ path: ":app", dir: "app" },
{ path: ":buildSrc", dir: "buildSrc", build: ":buildSrc" },
{ path: ":lib", dir: "/w/lib", build: ":lib" },
{ path: ":lib:core", dir: "/w/lib/core", build: ":lib" },
];
expect(gradleModuleGroups(projects, "Shop", "/w/shop").map(shape)).toEqual([{ label: "lib" }]);
// Beside includeFlat modules: the group (every `Shop.*` module), then the included build.
const both = [...projects, { path: ":svc", dir: "/w/svc" }];
expect(gradleModuleGroups(both, "Shop", "/w/shop").map(shape)).toEqual([
{
label: "Shop",
children: [
{ label: "app", detail: "Shop.app" },
{ label: "buildSrc", detail: "Shop.buildSrc" },
{ label: "svc", detail: "Shop.svc" },
],
},
{ label: "lib" },
]);
});
});
const dir = (path: string): FileEntry => ({
name: path.slice(path.lastIndexOf("/") + 1),
path,
kind: "dir",
size: 0,
mtimeMs: 1,
ignored: false,
excluded: false,
hidden: false,
});
describe("module group nodes in the tree", () => {
it("lists the group above the project and keeps a nested module whose parent is expanded", () => {
const listings = new Map([
["/w/billing", [dir("/w/billing/modules")]],
["/w/billing/modules", [dir("/w/billing/modules/core")]],
]);
const base = buildModel({ path: "/p", name: "p" }, new Map(), DEFAULT_TREE_OPTIONS);
const group = {
label: "Shop",
icon: "gradle-project",
beforeProject: true,
children: [
{ label: "billing", icon: "gradle-module", path: "/w/billing" },
{ label: "core", icon: "gradle-module", path: "/w/billing/modules/core" },
],
};
const m = withExtraRoots(base, [group], listings, DEFAULT_TREE_OPTIONS);
expect(m.roots.map((r) => r.label)).toEqual(["Shop", "p"]);
expect(m.children.get(m.roots[0]!.key)!.map((n) => n.label)).toEqual(["billing", "core"]);
});
});
it("namespaces module group identities by independently linked build", () => {
const projects = [
{ path: ":", dir: "/w/first" },
{ path: ":app", dir: "/w/shared" },
];
const first = gradleModuleGroups(projects, "same", "/w/project", "first");
const second = gradleModuleGroups(projects, "same", "/w/project", "second");
expect(first.length).toBeGreaterThan(0);
expect(first[0]!.id).not.toBe(second[0]!.id);
expect(first.map(shape)).toEqual(second.map(shape));
});