Skip to content

Commit 45024e8

Browse files
clydinhybrist
authored andcommitted
feat(@angular/cli): add unit test framework detection to list_projects tool
This commit enhances the `list_projects` MCP tool by adding the `unitTestFramework` field to the project output. This provides a clear signal to an AI model about which testing API (e.g., Jasmine, Jest, Vitest) to use when writing or modifying unit tests for a specific project. The detection logic handles: - The `@angular/build:unit-test` builder by inspecting its `runner` option. - Other builders by inferring the framework from the builder name (e.g., builders containing 'karma', 'jest'). - The experimental `@angular-devkit/build-angular:web-test-runner`. Additionally, the tool's description has been updated to guide the AI on how to use this new field, including instructions on how to proceed when the framework is 'unknown'.
1 parent 08e13a8 commit 45024e8

File tree

1 file changed

+64
-6
lines changed

1 file changed

+64
-6
lines changed

packages/angular/cli/src/commands/mcp/tools/projects.ts

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ const listProjectsOutputSchema = {
5353
'The prefix to use for component selectors.' +
5454
` For example, a prefix of 'app' would result in selectors like '<app-my-component>'.`,
5555
),
56+
unitTestFramework: z
57+
.enum(['jasmine', 'jest', 'vitest', 'unknown'])
58+
.optional()
59+
.describe(
60+
'The unit test framework used by the project, such as Jasmine, Jest, or Vitest. ' +
61+
'This field is critical for generating correct and idiomatic unit tests. ' +
62+
'When writing or modifying tests, you MUST use the APIs corresponding to this framework.',
63+
),
5664
}),
5765
),
5866
}),
@@ -84,21 +92,25 @@ export const LIST_PROJECTS_TOOL = declareTool({
8492
title: 'List Angular Projects',
8593
description: `
8694
<Purpose>
87-
Provides a comprehensive overview of all Angular workspaces and projects within a monorepo.
95+
Provides a comprehensive overview of all Angular workspaces and projects within the repository.
8896
It is essential to use this tool as a first step before performing any project-specific actions to understand the available projects,
8997
their types, and their locations.
9098
</Purpose>
9199
<Use Cases>
92100
* Finding the correct project name to use in other commands (e.g., \`ng generate component my-comp --project=my-app\`).
93101
* Identifying the \`root\` and \`sourceRoot\` of a project to read, analyze, or modify its files.
94-
* Determining if a project is an \`application\` or a \`library\`.
102+
* Determining a project's unit test framework (\`unitTestFramework\`) before writing or modifying tests.
95103
* Getting the \`selectorPrefix\` for a project before generating a new component to ensure it follows conventions.
96104
* Identifying the major version of the Angular framework for each workspace, which is crucial for monorepos.
97105
* Determining a project's primary function by inspecting its builder (e.g., '@angular-devkit/build-angular:browser' for an application).
98106
</Use Cases>
99107
<Operational Notes>
100108
* **Working Directory:** Shell commands for a project (like \`ng generate\`) **MUST**
101109
be executed from the parent directory of the \`path\` field for the relevant workspace.
110+
* **Unit Testing:** The \`unitTestFramework\` field tells you which testing API to use (e.g., Jasmine, Jest).
111+
If the value is 'unknown', you **MUST** inspect the project's configuration files
112+
(e.g., \`karma.conf.js\`, \`jest.config.js\`, or the 'test' target in \`angular.json\`) to determine the
113+
framework before generating tests.
102114
* **Disambiguation:** A monorepo may contain multiple workspaces (e.g., for different applications or even in output directories).
103115
Use the \`path\` of each workspace to understand its context and choose the correct project.
104116
</Operational Notes>`,
@@ -230,9 +242,55 @@ async function findAngularCoreVersion(
230242
return undefined;
231243
}
232244

233-
// Types for the structured output of the helper function.
245+
// Helper types inferred from the Zod schema for structured data processing.
234246
type WorkspaceData = z.infer<typeof listProjectsOutputSchema.workspaces>[number];
235247
type ParsingError = z.infer<typeof listProjectsOutputSchema.parsingErrors>[number];
248+
type VersioningError = z.infer<typeof listProjectsOutputSchema.versioningErrors>[number];
249+
250+
/**
251+
* Determines the unit test framework for a project based on its 'test' target configuration.
252+
* It handles both the modern `@angular/build:unit-test` builder with its `runner` option
253+
* and older builders where the framework is inferred from the builder name.
254+
* @param testTarget The 'test' target definition from the workspace configuration.
255+
* @returns The name of the unit test framework ('jasmine', 'jest', 'vitest'), 'unknown' if
256+
* the framework can't be determined from a known builder, or `undefined` if there is no test target.
257+
*/
258+
function getUnitTestFramework(
259+
testTarget: import('@angular-devkit/core').workspaces.TargetDefinition | undefined,
260+
): 'jasmine' | 'jest' | 'vitest' | 'unknown' | undefined {
261+
if (!testTarget) {
262+
return undefined;
263+
}
264+
265+
// For the new unit-test builder, the runner option directly informs the framework.
266+
if (testTarget.builder === '@angular/build:unit-test') {
267+
const runner = testTarget.options?.['runner'] as 'karma' | 'vitest' | undefined;
268+
if (runner === 'karma') {
269+
return 'jasmine'; // Karma is a runner, but the framework is Jasmine.
270+
} else {
271+
return runner; // For 'vitest', the runner and framework are the same.
272+
}
273+
}
274+
275+
// Fallback for older builders where the framework is inferred from the builder name.
276+
if (testTarget.builder) {
277+
const testBuilder = testTarget.builder;
278+
if (
279+
testBuilder.includes('karma') ||
280+
testBuilder === '@angular-devkit/build-angular:web-test-runner'
281+
) {
282+
return 'jasmine';
283+
} else if (testBuilder.includes('jest')) {
284+
return 'jest';
285+
} else if (testBuilder.includes('vitest')) {
286+
return 'vitest';
287+
} else {
288+
return 'unknown';
289+
}
290+
}
291+
292+
return undefined;
293+
}
236294

237295
/**
238296
* Loads, parses, and transforms a single angular.json file into the tool's output format.
@@ -255,13 +313,16 @@ async function loadAndParseWorkspace(
255313
const ws = await AngularWorkspace.load(configFile);
256314
const projects = [];
257315
for (const [name, project] of ws.projects.entries()) {
316+
const unitTestFramework = getUnitTestFramework(project.targets.get('test'));
317+
258318
projects.push({
259319
name,
260320
type: project.extensions['projectType'] as 'application' | 'library' | undefined,
261321
builder: project.targets.get('build')?.builder,
262322
root: project.root,
263323
sourceRoot: project.sourceRoot ?? path.posix.join(project.root, 'src'),
264324
selectorPrefix: project.extensions['prefix'] as string,
325+
unitTestFramework,
265326
});
266327
}
267328

@@ -278,9 +339,6 @@ async function loadAndParseWorkspace(
278339
}
279340
}
280341

281-
// Types for the structured output of the helper function.
282-
type VersioningError = z.infer<typeof listProjectsOutputSchema.versioningErrors>[number];
283-
284342
/**
285343
* Processes a single `angular.json` file to extract workspace and framework version information.
286344
* @param configFile The path to the `angular.json` file.

0 commit comments

Comments
 (0)