diff --git a/.github/workflows/very_good_cli.yaml b/.github/workflows/very_good_cli.yaml index 36856a30e..bb1516471 100644 --- a/.github/workflows/very_good_cli.yaml +++ b/.github/workflows/very_good_cli.yaml @@ -55,6 +55,7 @@ jobs: - test/src/commands/test/e2e # E2E tests for the create command - test/src/commands/create/e2e/flutter_app/core_test.dart + - test/src/commands/create/e2e/dart_package/dart_pkg_test.dart # E2E tests for the legacy create command syntax - test/src/commands/create/e2e/legacy/core_test.dart diff --git a/test/src/commands/create/commands/dart_package_test.dart b/test/src/commands/create/commands/dart_package_test.dart new file mode 100644 index 000000000..74224575e --- /dev/null +++ b/test/src/commands/create/commands/dart_package_test.dart @@ -0,0 +1,213 @@ +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:mason/mason.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; +import 'package:usage/usage.dart'; +import 'package:very_good_cli/src/commands/commands.dart'; +import '../../../../helpers/helpers.dart'; +import 'package:path/path.dart' as path; + +class MockAnalytics extends Mock implements Analytics {} + +class MockLogger extends Mock implements Logger {} + +class MockMasonGenerator extends Mock implements MasonGenerator {} + +class MockGeneratorHooks extends Mock implements GeneratorHooks {} + +class MockArgResults extends Mock implements ArgResults {} + +class FakeLogger extends Fake implements Logger {} + +class FakeDirectoryGeneratorTarget extends Fake + implements DirectoryGeneratorTarget {} + +final expectedUsage = [ + ''' +Creates a new very good Dart package in the specified directory. + +Usage: very_good create dart_package [arguments] +-h, --help Print this usage information. +-o, --output-directory The desired output directory when creating a new project. + --description The description for this new project. + (defaults to "A Very Good Project created by Very Good CLI.") + --publishable Whether the generated project is intended to be published. + +Run "very_good help" to see global options.''', +]; + +const pubspec = ''' +name: example +environment: + sdk: ">=2.13.0 <3.0.0" +'''; + +void main() { + late Analytics analytics; + late Logger logger; + + setUpAll(() { + registerFallbackValue(FakeDirectoryGeneratorTarget()); + registerFallbackValue(FakeLogger()); + }); + + setUp(() { + analytics = MockAnalytics(); + when( + () => analytics.sendEvent(any(), any(), label: any(named: 'label')), + ).thenAnswer((_) async {}); + when( + () => analytics.waitForLastPing(timeout: any(named: 'timeout')), + ).thenAnswer((_) async {}); + + logger = MockLogger(); + + final progress = MockProgress(); + + when(() => logger.progress(any())).thenReturn(progress); + }); + + group('can be instantiated', () { + test('with default options', () { + final logger = Logger(); + final command = CreateDartPackage( + analytics: analytics, + logger: logger, + generatorFromBundle: null, + generatorFromBrick: null, + ); + expect(command.name, equals('dart_package')); + expect( + command.description, + equals( + 'Creates a new very good Dart package in the specified directory.', + ), + ); + expect(command.logger, equals(logger)); + expect(command, isA()); + }); + }); + + group('create dart_package', () { + test( + 'help', + withRunner((commandRunner, logger, pubUpdater, printLogs) async { + final result = + await commandRunner.run(['create', 'dart_package', '--help']); + expect(printLogs, equals(expectedUsage)); + expect(result, equals(ExitCode.success.code)); + + printLogs.clear(); + + final resultAbbr = + await commandRunner.run(['create', 'dart_pkg', '-h']); + expect(printLogs, equals(expectedUsage)); + expect(resultAbbr, equals(ExitCode.success.code)); + }), + ); + + group('running the command', () { + final generatedFiles = + List.filled(10, const GeneratedFile.created(path: '')); + + late GeneratorHooks hooks; + late MasonGenerator generator; + + setUp(() { + hooks = MockGeneratorHooks(); + generator = MockMasonGenerator(); + + when(() => generator.hooks).thenReturn(hooks); + when( + () => hooks.preGen( + vars: any(named: 'vars'), + onVarsChanged: any(named: 'onVarsChanged'), + ), + ).thenAnswer((_) async {}); + + when( + () => generator.generate( + any(), + vars: any(named: 'vars'), + logger: any(named: 'logger'), + ), + ).thenAnswer((_) async { + return generatedFiles; + }); + + when(() => generator.id).thenReturn('generator_id'); + when(() => generator.description).thenReturn('generator description'); + when(() => generator.hooks).thenReturn(hooks); + + when( + () => hooks.preGen( + vars: any(named: 'vars'), + onVarsChanged: any(named: 'onVarsChanged'), + ), + ).thenAnswer((_) async {}); + when( + () => generator.generate( + any(), + vars: any(named: 'vars'), + logger: any(named: 'logger'), + ), + ).thenAnswer((_) async { + final target = + _.positionalArguments.first as DirectoryGeneratorTarget; + File(path.join(target.dir.path, 'my_package', 'pubspec.yaml')) + ..createSync(recursive: true) + ..writeAsStringSync(pubspec); + return generatedFiles; + }); + }); + + test('creates dart package', () async { + final tempDir = Directory.systemTemp.createTempSync(); + addTearDown(() => tempDir.deleteSync(recursive: true)); + final argResults = MockArgResults(); + final command = CreateDartPackage( + analytics: analytics, + logger: logger, + generatorFromBundle: (_) async => throw Exception('oops'), + generatorFromBrick: (_) async => generator, + )..argResultOverrides = argResults; + when(() => argResults['output-directory'] as String?) + .thenReturn(tempDir.path); + when(() => argResults.rest).thenReturn(['my_package']); + + final result = await command.run(); + + expect(command.template.name, 'dart_pkg'); + expect(result, equals(ExitCode.success.code)); + + verify(() => logger.progress('Bootstrapping')).called(1); + verify( + () => hooks.preGen( + vars: { + 'project_name': 'my_package', + 'description': '', + 'publishable': false, + }, + onVarsChanged: any(named: 'onVarsChanged'), + ), + ); + verify( + () => generator.generate( + any(), + vars: { + 'project_name': 'my_package', + 'description': '', + 'publishable': false, + }, + logger: logger, + ), + ).called(1); + verify( + () => logger.info('Created a Very Good Dart Package! 🦄'), + ).called(1); + }); + }); + }); +} diff --git a/test/src/commands/create/e2e/dart_package/dart_pkg_test.dart b/test/src/commands/create/e2e/dart_package/dart_pkg_test.dart new file mode 100644 index 000000000..856906371 --- /dev/null +++ b/test/src/commands/create/e2e/dart_package/dart_pkg_test.dart @@ -0,0 +1,61 @@ +@Tags(['e2e']) +import 'package:mason/mason.dart'; +import 'package:path/path.dart' as path; +import 'package:test/test.dart'; +import 'package:universal_io/io.dart'; + +import '../../../../../helpers/helpers.dart'; + +void main() { + test( + 'create dart_package', + withRunner((commandRunner, logger, updater, logs) async { + final directory = Directory.systemTemp.createTempSync(); + + final result = await commandRunner.run( + ['create', 'dart_package', 'very_good_dart', '-o', directory.path], + ); + expect(result, equals(ExitCode.success.code)); + + final formatResult = await Process.run( + 'flutter', + ['format', '--set-exit-if-changed', '.'], + workingDirectory: path.join(directory.path, 'very_good_dart'), + runInShell: true, + ); + expect(formatResult.exitCode, equals(ExitCode.success.code)); + expect(formatResult.stderr, isEmpty); + + final analyzeResult = await Process.run( + 'flutter', + ['analyze', '.'], + workingDirectory: path.join(directory.path, 'very_good_dart'), + runInShell: true, + ); + expect(analyzeResult.exitCode, equals(ExitCode.success.code)); + expect(analyzeResult.stderr, isEmpty); + expect(analyzeResult.stdout, contains('No issues found!')); + + final testResult = await Process.run( + 'flutter', + ['test', '--no-pub', '--coverage'], + workingDirectory: path.join(directory.path, 'very_good_dart'), + runInShell: true, + ); + expect(testResult.exitCode, equals(ExitCode.success.code)); + expect(testResult.stderr, isEmpty); + expect(testResult.stdout, contains('All tests passed!')); + + final testCoverageResult = await Process.run( + 'genhtml', + ['coverage/lcov.info', '-o', 'coverage'], + workingDirectory: path.join(directory.path, 'very_good_dart'), + runInShell: true, + ); + expect(testCoverageResult.exitCode, equals(ExitCode.success.code)); + expect(testCoverageResult.stderr, isEmpty); + expect(testCoverageResult.stdout, contains('lines......: 100.0%')); + }), + timeout: const Timeout(Duration(minutes: 2)), + ); +}