Skip to content

Commit

Permalink
feat: create dart package new usage (#611)
Browse files Browse the repository at this point in the history
* feat: create dart package new usage

* tests
  • Loading branch information
renancaraujo committed Jan 9, 2023
1 parent e01a127 commit aa391d6
Show file tree
Hide file tree
Showing 10 changed files with 580 additions and 2 deletions.
1 change: 1 addition & 0 deletions .github/workflows/very_good_cli.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions lib/src/commands/create/commands/commands.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export 'create_subcommand.dart';
export 'dart_package.dart';
export 'flutter_app.dart';
export 'legacy.dart';
19 changes: 19 additions & 0 deletions lib/src/commands/create/commands/create_subcommand.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ abstract class CreateSubCommand extends Command<int> {
aliases: ['org'],
);
}

if (this is Publishable) {
argParser.addFlag(
'publishable',
negatable: false,
help: 'Whether the generated project is intended to be published.',
);
}
}

final Analytics _analytics;
Expand Down Expand Up @@ -236,6 +244,7 @@ abstract class CreateSubCommand extends Command<int> {
'project_name': projectName,
'description': projectDescription,
if (this is OrgName) 'org_name': (this as OrgName).orgName,
if (this is Publishable) 'publishable': (this as Publishable).publishable,
};
}
}
Expand Down Expand Up @@ -298,3 +307,13 @@ mixin MultiTemplates on CreateSubCommand {
);
}
}

/// Mixin for [CreateSubCommand] subclasses that receives the publishable
/// flag.
///
/// Takes care of parsing it from [argResults] and pass it
/// to the brick generator.
mixin Publishable on CreateSubCommand {
/// Gets the publishable flag.
bool get publishable => argResults['publishable'] as bool? ?? false;
}
35 changes: 35 additions & 0 deletions lib/src/commands/create/commands/dart_package.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import 'package:mason_logger/mason_logger.dart';
import 'package:usage/usage.dart';
import 'package:very_good_cli/src/commands/commands.dart';
import 'package:very_good_cli/src/commands/create/templates/templates.dart';

/// {@template very_good_create_dart_package_command}
/// A [CreateSubCommand] for creating Dart packages.
/// {@endtemplate}
class CreateDartPackage extends CreateSubCommand with Publishable {
/// {@macro very_good_create_dart_package_command}
CreateDartPackage({
required Analytics analytics,
required Logger logger,
required MasonGeneratorFromBundle? generatorFromBundle,
required MasonGeneratorFromBrick? generatorFromBrick,
}) : super(
analytics: analytics,
logger: logger,
generatorFromBundle: generatorFromBundle,
generatorFromBrick: generatorFromBrick,
);

@override
String get name => 'dart_package';

@override
List<String> get aliases => ['dart_pkg'];

@override
String get description =>
'Creates a new very good Dart package in the specified directory.';

@override
Template get template => DartPkgTemplate();
}
10 changes: 10 additions & 0 deletions lib/src/commands/create/create.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ class CreateCommand extends Command<int> {
generatorFromBrick: generatorFromBrick,
),
);

// very_good create dart_pkg <args>
addSubcommand(
CreateDartPackage(
analytics: analytics,
logger: logger,
generatorFromBundle: generatorFromBundle,
generatorFromBrick: generatorFromBrick,
),
);
}

@override
Expand Down
214 changes: 214 additions & 0 deletions test/src/commands/create/commands/dart_package_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import 'dart:io';

import 'package:args/args.dart';
import 'package:mason/mason.dart';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
import 'package:usage/usage.dart';
import 'package:very_good_cli/src/commands/commands.dart';

import '../../../../helpers/helpers.dart';

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 <project-name> [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<Publishable>());
});
});

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: <String, dynamic>{
'project_name': 'my_package',
'description': '',
'publishable': false,
},
onVarsChanged: any(named: 'onVarsChanged'),
),
);
verify(
() => generator.generate(
any(),
vars: <String, dynamic>{
'project_name': 'my_package',
'description': '',
'publishable': false,
},
logger: logger,
),
).called(1);
verify(
() => logger.info('Created a Very Good Dart Package! 馃'),
).called(1);
});
});
});
}
3 changes: 2 additions & 1 deletion test/src/commands/create/commands/legacy_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ Usage: very_good create <subcommand> <project-name> [arguments]
-h, --help Print this usage information.
Available subcommands:
flutter_app Creates a new very good Flutter app in the specified directory.
dart_package Creates a new very good Dart package in the specified directory.
flutter_app Creates a new very good Flutter app in the specified directory.
Run "very_good help" to see global options.'''
];
Expand Down
Loading

0 comments on commit aa391d6

Please sign in to comment.