blob: 25f405f1a92449fd496a8453b66aa88a153bf9f9 [file]
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:api_summary/api_summary.dart';
import 'package:test/test.dart';
import 'package:test_descriptor/test_descriptor.dart' as d;
import 'package:yaml_edit/yaml_edit.dart';
void main() {
group('ApiSummary environment and executables', () {
test('sorts environment and executables keys alphabetically', () {
final summary = ApiSummary(
name: 'test_pkg',
environment: {
'sdk': '^3.12.0',
'flutter': '>=3.2.0 <3.9.0',
'fuchsia': '>=1.0.0',
},
executables: {'z_tool': null, 'a_tool': 'main_a', 'm_tool': 'm_tool'},
libraries: [],
);
expect(
summary.environment.keys.toList(),
equals(['flutter', 'fuchsia', 'sdk']),
);
expect(
summary.executables.keys.toList(),
equals(['a_tool', 'm_tool', 'z_tool']),
);
});
test(
'produces identical output regardless of input map insertion order',
() {
final summaryA = ApiSummary(
name: 'test_pkg',
environment: {'sdk': '^3.12.0', 'flutter': '>=3.2.0 <3.9.0'},
executables: {'z_tool': null, 'a_tool': 'main_a'},
libraries: [],
);
final summaryB = ApiSummary(
name: 'test_pkg',
environment: {'flutter': '>=3.2.0 <3.9.0', 'sdk': '^3.12.0'},
executables: {'a_tool': 'main_a', 'z_tool': null},
libraries: [],
);
expect(summaryA.toString(), equals(summaryB.toString()));
expect(summaryA.toJson(), equals(summaryB.toJson()));
},
);
test('toJson and fromJson round-trip with environment and executables', () {
final summary = ApiSummary(
name: 'test_pkg',
environment: {'sdk': '^3.12.0', 'flutter': '>=3.2.0 <3.9.0'},
executables: {'cli_a': null, 'cli_b': 'custom_b'},
libraries: [],
);
final json = summary.toJson();
expect(json['name'], equals('test_pkg'));
expect(
json['environment'],
equals({'flutter': '>=3.2.0 <3.9.0', 'sdk': '^3.12.0'}),
);
expect(json['executables'], equals({'cli_a': null, 'cli_b': 'custom_b'}));
final rehydrated = ApiSummary.fromJson(json);
expect(rehydrated.name, equals('test_pkg'));
expect(rehydrated.environment, equals(summary.environment));
expect(rehydrated.executables, equals(summary.executables));
});
test('toJson omits environment and executables when empty', () {
final summary = ApiSummary(name: 'test_pkg', libraries: []);
final json = summary.toJson();
expect(json.containsKey('environment'), isFalse);
expect(json.containsKey('executables'), isFalse);
final rehydrated = ApiSummary.fromJson(json);
expect(rehydrated.environment, isEmpty);
expect(rehydrated.executables, isEmpty);
});
test('text rendering formats environment and executables', () {
final summary = ApiSummary(
name: 'test_pkg',
environment: {'sdk': '^3.12.0', 'flutter': '>=3.2.0 <3.9.0'},
executables: {
'simple_tool': null,
'same_name_tool': 'same_name_tool',
'custom_tool': 'custom_script',
},
libraries: [],
);
final rendered = summary.toString();
expect(
rendered,
equals(
'environment:\n'
' flutter: >=3.2.0 <3.9.0\n'
' sdk: ^3.12.0\n'
'executables:\n'
' custom_tool: custom_script\n'
' same_name_tool\n'
' simple_tool\n',
),
);
});
test('yaml serialization renders environment and executables', () {
final summary = ApiSummary(
name: 'test_pkg',
environment: {'sdk': '^3.12.0'},
executables: {'my_tool': null},
libraries: [],
);
final editor = YamlEditor('');
editor.update([], summary.toJson());
final yaml = '$editor\n';
expect(yaml, contains('environment:\n sdk: ^3.12.0'));
expect(yaml, contains('executables:\n my_tool: null'));
});
});
group('pubspec.yaml parsing in apiSummary', () {
Future<void> withTempPkg(
String pubspecContent,
Future<void> Function(String pkgPath) fn,
) async {
await d.dir('pkg', [
d.file('pubspec.yaml', pubspecContent),
d.dir('lib', [d.file('foo.dart', 'void foo() {}')]),
]).create();
await fn(d.path('pkg'));
}
test('extracts environment and executables successfully', () async {
await withTempPkg(
'''
name: sample_pkg
environment:
sdk: ^3.12.0
flutter: ">=3.2.0 <3.9.0"
executables:
tool_a:
tool_b: custom_b
''',
(pkgPath) async {
final summary = await apiSummary(pkgPath);
expect(summary.name, equals('sample_pkg'));
expect(
summary.environment,
equals({'flutter': '>=3.2.0 <3.9.0', 'sdk': '^3.12.0'}),
);
expect(
summary.executables,
equals({'tool_a': null, 'tool_b': 'custom_b'}),
);
},
);
});
test('handles omitted and null environment and executables', () async {
await withTempPkg(
'''
name: minimal_pkg
environment:
executables:
''',
(pkgPath) async {
final summary = await apiSummary(pkgPath);
expect(summary.name, equals('minimal_pkg'));
expect(summary.environment, isEmpty);
expect(summary.executables, isEmpty);
},
);
});
test('ignores null environment constraints', () async {
await withTempPkg(
'''
name: sample_pkg
environment:
sdk: ^3.12.0
flutter:
''',
(pkgPath) async {
final summary = await apiSummary(pkgPath);
expect(summary.environment, equals({'sdk': '^3.12.0'}));
},
);
});
test('throws FormatException when pubspec is not a YAML map', () async {
await withTempPkg('- item1\n- item2', (pkgPath) async {
await expectLater(
apiSummary(pkgPath),
throwsA(
isA<FormatException>().having(
(e) => e.message,
'message',
contains('Expected pubspec to be a YAML map'),
),
),
);
});
});
test(
'throws FormatException when name is missing or not a string',
() async {
await withTempPkg(
'''
environment:
sdk: ^3.12.0
''',
(pkgPath) async {
await expectLater(
apiSummary(pkgPath),
throwsA(
isA<FormatException>().having(
(e) => e.message,
'message',
contains('Expected pubspec to contain a "name" string'),
),
),
);
},
);
await withTempPkg('name: 12345', (pkgPath) async {
await expectLater(
apiSummary(pkgPath),
throwsA(
isA<FormatException>().having(
(e) => e.message,
'message',
contains('Expected pubspec to contain a "name" string'),
),
),
);
});
},
);
test('throws FormatException when environment is not a map', () async {
await withTempPkg('name: foo\nenvironment: "sdk ^3.12.0"', (
pkgPath,
) async {
await expectLater(
apiSummary(pkgPath),
throwsA(
isA<FormatException>().having(
(e) => e.message,
'message',
contains('Expected "environment" to be a YAML map'),
),
),
);
});
});
test(
'throws FormatException when environment constraint is not a scalar',
() async {
await withTempPkg(
'''
name: foo
environment:
sdk: [3, 0]
''',
(pkgPath) async {
await expectLater(
apiSummary(pkgPath),
throwsA(
isA<FormatException>().having(
(e) => e.message,
'message',
contains(
'Expected environment constraint for "sdk" '
'to be a string or scalar',
),
),
),
);
},
);
await withTempPkg(
'''
name: foo
environment:
sdk:
major: 3
''',
(pkgPath) async {
await expectLater(
apiSummary(pkgPath),
throwsA(
isA<FormatException>().having(
(e) => e.message,
'message',
contains(
'Expected environment constraint for "sdk" '
'to be a string or scalar',
),
),
),
);
},
);
},
);
test('throws FormatException when executables is not a map', () async {
await withTempPkg('name: foo\nexecutables: "my_tool"', (pkgPath) async {
await expectLater(
apiSummary(pkgPath),
throwsA(
isA<FormatException>().having(
(e) => e.message,
'message',
contains('Expected "executables" to be a YAML map'),
),
),
);
});
});
test(
'throws FormatException when executable target is not string or null',
() async {
await withTempPkg(
'''
name: foo
executables:
my_tool: [invalid, target]
''',
(pkgPath) async {
await expectLater(
apiSummary(pkgPath),
throwsA(
isA<FormatException>().having(
(e) => e.message,
'message',
contains(
'Expected executable target for "my_tool" '
'to be a string or null',
),
),
),
);
},
);
},
);
});
}