diff --git a/src/features/run/services.test.ts b/src/features/run/services.test.ts index cf827b356..dcef82615 100644 --- a/src/features/run/services.test.ts +++ b/src/features/run/services.test.ts @@ -14,6 +14,7 @@ vi.mock("@/platform/ipc", async (importOriginal) => ({ import { addType, cleanupRemovedProject, + addMenuCategories, categorizedTypes, runServiceProvider, registerRunServiceActions, @@ -450,6 +451,13 @@ describe("add service categories", () => { it("groups nothing for an empty list", () => { expect(categorizedTypes([])).toEqual([]); }); + + it("lists a category once in the Add Service menu when the project uses only some of its kinds", () => { + const { project, rest } = addMenuCategories(["go", "maven", "gradle", "python"], ["go", "maven"]); + expect(project.map((b) => b.category)).toEqual(["Build Tools", "Go"]); + expect(project[0]!.kinds).toEqual(expect.arrayContaining(["maven", "gradle"])); + expect(rest.map((b) => b.category)).toEqual(["Python"]); + }); }); describe("open in browser", () => { diff --git a/src/features/run/services.tsx b/src/features/run/services.tsx index b35c3dd34..6e739f726 100644 --- a/src/features/run/services.tsx +++ b/src/features/run/services.tsx @@ -835,6 +835,13 @@ export function categorizedTypes(kinds: readonly string[]): Array<{ category: st })); } +/** The project's categories, then the rest. A category appears once, even when the project uses only some of its kinds. */ +export function addMenuCategories(kinds: readonly string[], used: readonly string[]) { + const buckets = categorizedTypes(kinds); + const inProject = (bucket: (typeof buckets)[number]) => bucket.kinds.some((kind) => used.includes(kind)); + return { project: buckets.filter(inProject), rest: buckets.filter((bucket) => !inProject(bucket)) }; +} + /** "Add Service › Run Configuration Type": the project's types first, then the rest. */ function addMenu(): MenuItem[] { const added = currentTypes(); @@ -849,18 +856,17 @@ function addMenu(): MenuItem[] { await executeAction("run.services.addType", kind); }, }); - const grouped = (list: readonly string[]) => - categorizedTypes(list).map(({ category, kinds: members }) => ({ + const grouped = (buckets: ReturnType) => + buckets.map(({ category, kinds: members }) => ({ label: category, icon: configIcon({ kind: members[0]!, isTest: false }), submenu: members.map(item), })); + const { project, rest } = addMenuCategories(kinds, used); const submenu: MenuItem[] = [ - ...grouped(kinds.filter((k) => used.includes(k))), - ...(used.some((k) => kinds.includes(k)) && kinds.some((k) => !used.includes(k)) - ? [{ kind: "separator" } as MenuItem] - : []), - ...grouped(kinds.filter((k) => !used.includes(k))), + ...grouped(project), + ...(project.length && rest.length ? [{ kind: "separator" } as MenuItem] : []), + ...grouped(rest), ]; return [{ label: "Run Configuration Type", icon: "toolwindow-run", disabled: !submenu.length, submenu }]; }