Skip to content

Commit

Permalink
feat: create dart package new usage
Browse files Browse the repository at this point in the history
  • Loading branch information
renancaraujo committed Jan 6, 2023
1 parent fbd74fd commit 79abe27
Show file tree
Hide file tree
Showing 7 changed files with 304 additions and 2 deletions.
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
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
235 changes: 235 additions & 0 deletions test/src/commands/create/create_subcommand_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,23 @@ class _TestCreateSubCommandWithOrgName extends _TestCreateSubCommand
);
}

class _TestCreateSubCommandWithPublishable extends _TestCreateSubCommand
with Publishable {
_TestCreateSubCommandWithPublishable({
required Template template,
required Analytics analytics,
required Logger logger,
required MasonGeneratorFromBundle? generatorFromBundle,
required MasonGeneratorFromBrick? generatorFromBrick,
}) : super(
template: template,
analytics: analytics,
logger: logger,
generatorFromBundle: generatorFromBundle,
generatorFromBrick: generatorFromBrick,
);
}

class _TestCreateSubCommandMultiTemplate extends CreateSubCommand
with MultiTemplates {
_TestCreateSubCommandMultiTemplate({
Expand Down Expand Up @@ -1082,4 +1099,222 @@ Run "runner help" to see global options.''';
});
});
});

group('Publishable', () {
const expectedUsage = '''
Usage: very_good create create_subcommand <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 "runner help" to see global options.''';

late Template template;
late MockBundle bundle;

setUp(() {
bundle = MockBundle();
when(() => bundle.name).thenReturn('test');
when(() => bundle.description).thenReturn('Test bundle');
when(() => bundle.version).thenReturn('<bundleversion>');
template = MockTemplate();
when(() => template.name).thenReturn('test');
when(() => template.bundle).thenReturn(bundle);
when(() => template.onGenerateComplete(any(), any())).thenAnswer(
(_) async {},
);
when(
() => analytics.sendEvent(any(), any(), label: any(named: 'label')),
).thenAnswer((_) async {});
});

group('can be instantiated', () {
test('with default options', () {
final command = _TestCreateSubCommandWithPublishable(
template: template,
analytics: analytics,
logger: logger,
generatorFromBundle: null,
generatorFromBrick: null,
);

expect(
command.argParser.options['publishable'],
isA<Option>()
.having((o) => o.isFlag, 'isFlag', true)
.having((o) => o.abbr, 'abbr', null)
.having((o) => o.defaultsTo, 'defaultsTo', false)
.having((o) => o.aliases, 'aliases', <String>[]),
);
expect(command.argParser.commands, isEmpty);
});
});

group('parsing of options', () {
late GeneratorHooks hooks;
late MasonGenerator generator;
late _TestCommandRunner runner;

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 {
return generatedFiles;
});

final command = _TestCreateSubCommandWithPublishable(
template: template,
analytics: analytics,
logger: logger,
generatorFromBundle: (_) async => throw Exception('oops'),
generatorFromBrick: (_) async => generator,
);

runner = _TestCommandRunner(command: command);
});

test('parses publishable', () async {
final result = await runner.run([
'create_subcommand',
'test_project',
'--publishable',
]);

expect(result, equals(ExitCode.success.code));

verify(
() => hooks.preGen(
vars: any(
named: 'vars',
that: isA<Map<String, dynamic>>().having(
(description) => description['publishable'],
'publishable',
true,
),
),
onVarsChanged: any(named: 'onVarsChanged'),
),
);

verify(
() => generator.generate(
any(
that: isA<DirectoryGeneratorTarget>(),
),
vars: any(
named: 'vars',
that: isA<Map>().having(
(vars) {
return vars['publishable'];
},
'publishable',
true,
),
),
logger: logger,
),
).called(1);
});

test('uses default values for omitted options', () async {
final result = await runner.run([
'create_subcommand',
'test_project',
]);

expect(result, equals(ExitCode.success.code));

verify(
() => hooks.preGen(
vars: any(
named: 'vars',
that: isA<Map<String, dynamic>>().having(
(description) => description['publishable'],
'publishable',
false,
),
),
onVarsChanged: any(named: 'onVarsChanged'),
),
);

verify(
() => generator.generate(
any(
that: isA<DirectoryGeneratorTarget>(),
),
vars: any(
named: 'vars',
that: isA<Map>().having(
(description) => description['publishable'],
'publishable',
false,
),
),
logger: logger,
),
).called(1);
});

group('validates publishable', () {
test('throws UsageException when --template is invalid', () async {
await expectLater(
() async {
await runner.run([
'create_subcommand',
'test_project',
'--no-publishable',
]);
},
throwsA(
isA<UsageException>()
.having((e) => e.usage, 'usage', expectedUsage)
.having(
(e) => e.message,
'message',
'Cannot negate option "no-publishable".',
),
),
);
});
});
});
});
}
3 changes: 2 additions & 1 deletion test/src/commands/create/create_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,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

0 comments on commit 79abe27

Please sign in to comment.