Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions lib/src/mcp/mcp_server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,14 @@ If is omitted, then core will be selected.
'Target directory path (defaults to current directory). '
'Can be absolute or relative path to project root.',
),
'paths': ListSchema(
description:
'Test files or directories to run, relative to the project '
"root (e.g. ['test/src/foo_test.dart', 'test/widgets']). "
'When omitted, the whole suite runs. Note that targeting '
'specific paths disables the test optimization step.',
items: StringSchema(),
),
'dart': BooleanSchema(
description:
'''Whether to run Dart tests. If not specified, Flutter tests will be run if a Flutter project is detected.''',
Expand Down Expand Up @@ -452,6 +460,13 @@ Only one value can be selected.
]);
}

// Positional test targets go last, after every option, so that they are
// parsed as `rest` rather than as a value for the preceding option.
final paths = args['paths'] as List<Object?>?;
if (paths != null) {
cliArgs.addAll(paths.cast<String>());
}

return cliArgs;
}

Expand Down
63 changes: 63 additions & 0 deletions test/src/mcp/mcp_server_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,69 @@ void main() {
as List<String>;
expect(capturedArgs, equals(['test', '--timeout', '120']));
});

test('passes paths as positional test targets', () async {
await sendRequest(
CallToolRequest.methodName,
_params(
CallToolRequest(
name: 'test',
arguments: {
'paths': ['test/src/foo_test.dart', 'test/widgets'],
},
),
),
);

final capturedArgs =
verify(() => mockCommandRunner.run(captureAny())).captured.first
as List<String>;
expect(
capturedArgs,
equals(['test', 'test/src/foo_test.dart', 'test/widgets']),
);
});

test('passes paths after options so they are parsed as rest', () async {
await sendRequest(
CallToolRequest.methodName,
_params(
CallToolRequest(
name: 'test',
arguments: {
'dart': true,
'concurrency': '8',
'paths': ['test/src/foo_test.dart'],
},
),
),
);

final capturedArgs =
verify(() => mockCommandRunner.run(captureAny())).captured.first
as List<String>;
expect(
capturedArgs,
equals(['dart', 'test', '-j', '8', 'test/src/foo_test.dart']),
);
});

test('adds no positional targets when paths is empty', () async {
await sendRequest(
CallToolRequest.methodName,
_params(
CallToolRequest(
name: 'test',
arguments: {'paths': <String>[]},
),
),
);

final capturedArgs =
verify(() => mockCommandRunner.run(captureAny())).captured.first
as List<String>;
expect(capturedArgs, equals(['test']));
});
});

group('Tool: packages_get', () {
Expand Down