Skip to content
Merged
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
9 changes: 5 additions & 4 deletions lib/src/commands/create/commands/create_subcommand.dart
Original file line number Diff line number Diff line change
Expand Up @@ -287,15 +287,16 @@ mixin OrgName on CreateSubCommand {

/// Mixin for [CreateSubCommand] subclasses that receives multiple templates.
///
/// Subcommands that mix with this mixin should override [templates] and
/// [defaultTemplateName];
/// Subcommands that mix with this mixin should override [templates].
///
/// Takes care of parsing the desired template from [argResults] and
/// validating the org name.
mixin MultiTemplates on CreateSubCommand {
/// Gets the desired template to be created during a command run when the
/// template argument is not provided.`
String get defaultTemplateName;
/// template argument is not provided.
///
/// Defaults to the first template in [templates].
String get defaultTemplateName => templates.first.name;

/// Gets all the templates to be created during a command run.
List<Template> get templates;
Expand Down
11 changes: 7 additions & 4 deletions lib/src/commands/create/commands/flutter_app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'package:very_good_cli/src/commands/create/templates/templates.dart';
/// {@template very_good_create_flutter_app_command}
/// A [CreateSubCommand] for creating Flutter apps.
/// {@endtemplate}
class CreateFlutterApp extends CreateSubCommand with OrgName {
class CreateFlutterApp extends CreateSubCommand with OrgName, MultiTemplates {
/// {@macro very_good_create_flutter_app_command}
CreateFlutterApp({
required super.analytics,
Expand All @@ -25,9 +25,6 @@ class CreateFlutterApp extends CreateSubCommand with OrgName {
@override
String get description => 'Generate a Very Good Flutter application.';

@override
Template get template => VeryGoodCoreTemplate();

@override
Map<String, dynamic> getTemplateVars() {
final vars = super.getTemplateVars();
Expand All @@ -39,4 +36,10 @@ class CreateFlutterApp extends CreateSubCommand with OrgName {

return vars;
}

@override
final List<Template> templates = [
VeryGoodCoreTemplate(),
VeryGoodWearAppTemplate(),
];
}
1 change: 1 addition & 0 deletions lib/src/commands/create/templates/templates.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export 'very_good_docs_site/very_good_docs_site.dart';
export 'very_good_flame_game/very_good_flame_game.dart';
export 'very_good_flutter_package/very_good_flutter_package.dart';
export 'very_good_flutter_plugin/very_good_flutter_plugin.dart';
export 'very_good_wear_app/very_good_wear_app.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export 'very_good_wear_app_bundle.dart';
export 'very_good_wear_app_template.dart';

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'dart:io';

import 'package:mason/mason.dart';
import 'package:very_good_cli/src/commands/create/templates/templates.dart';
import 'package:very_good_cli/src/logger_extension.dart';

/// {@template wear_app_template}
/// A template for Wear OS apps.
/// {@endtemplate}
class VeryGoodWearAppTemplate extends Template {
/// {@macro wear_app_template}
VeryGoodWearAppTemplate()
: super(
name: 'wear',
bundle: veryGoodWearAppBundle,
help: 'Generate a Very Good Flutter Wear OS application.',
);

@override
Future<void> onGenerateComplete(Logger logger, Directory outputDir) async {
await installFlutterPackages(logger, outputDir);
await applyDartFixes(logger, outputDir);
_logSummary(logger);
}

void _logSummary(Logger logger) {
logger
..info('\n')
..created('Created a Very Good Wear OS app! ⌚️🦄')
..info('\n');
}
}
59 changes: 59 additions & 0 deletions test/helpers/test_multi_template_commands.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
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:very_good_cli/src/commands/commands.dart';
import 'package:very_good_cli/src/logger_extension.dart';

class _MockArgResults extends Mock implements ArgResults {}

Future<void> testMultiTemplateCommand({
required MultiTemplates multiTemplatesCommand,
required String templateName,
required Map<String, dynamic> mockArgs,
required MasonGenerator generator,
required Logger logger,
required GeneratorHooks hooks,

// expected
required Map<String, dynamic> expectedVars,
required String expectedLogSummary,
}) async {
final tempDir = Directory.systemTemp.createTempSync();
addTearDown(() => tempDir.deleteSync(recursive: true));
final argResults = _MockArgResults();
final command = multiTemplatesCommand..argResultOverrides = argResults;

when(() => argResults['template'] as String?).thenReturn(templateName);
when(() => argResults['output-directory'] as String?)
.thenReturn(tempDir.path);

for (final entry in mockArgs.entries) {
when(() => argResults[entry.key]).thenReturn(entry.value);
}

when(() => argResults.rest).thenReturn(['my_app']);

final result = await command.run();

expect(command.template.name, templateName);
expect(result, equals(ExitCode.success.code));

verify(() => logger.progress('Bootstrapping')).called(1);
verify(
() => hooks.preGen(
vars: expectedVars,
onVarsChanged: any(named: 'onVarsChanged'),
),
);
verify(
() => generator.generate(
any(),
vars: expectedVars,
logger: logger,
),
).called(1);
verify(() => logger.created(expectedLogSummary)).called(1);
}
122 changes: 55 additions & 67 deletions test/src/commands/create/commands/flutter_app_test.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
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 'package:very_good_cli/src/commands/create/commands/flutter_app.dart';

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

class MockAnalytics extends Mock implements Analytics {}

Expand All @@ -18,8 +19,6 @@ 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
Expand All @@ -30,13 +29,18 @@ final expectedUsage = [
Generate a Very Good Flutter application.

Usage: very_good create flutter_app <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.")
--org-name The organization for this new project.
(defaults to "com.example.verygoodcore")
--application-id The bundle identifier on iOS or application id on Android. (defaults to <org-name>.<project-name>)
-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.")
-t, --template The template used to generate this new project.

[core] (default) Generate a Very Good Flutter application.
[wear] Generate a Very Good Flutter Wear OS application.

--org-name The organization for this new project.
(defaults to "com.example.verygoodcore")
--application-id The bundle identifier on iOS or application id on Android. (defaults to <org-name>.<project-name>)

Run "very_good help" to see global options.''',
];
Expand All @@ -52,6 +56,8 @@ void main() {
late Analytics analytics;
late Logger logger;

final generatedFiles = List.filled(10, const GeneratedFile.created(path: ''));

setUpAll(() {
registerFallbackValue(FakeDirectoryGeneratorTarget());
registerFallbackValue(FakeLogger());
Expand Down Expand Up @@ -116,9 +122,6 @@ void main() {
);

group('running the command', () {
final generatedFiles =
List.filled(10, const GeneratedFile.created(path: ''));

late GeneratorHooks hooks;
late MasonGenerator generator;

Expand All @@ -134,16 +137,6 @@ void main() {
),
).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);
Expand All @@ -170,59 +163,54 @@ void main() {
});
});

test('create core app', () async {
final tempDir = Directory.systemTemp.createTempSync();
addTearDown(() => tempDir.deleteSync(recursive: true));
final argResults = MockArgResults();
final command = CreateFlutterApp(
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_app']);
when(() => argResults['application-id'] as String?).thenReturn(
'xyz.app.my_app',
);

final result = await command.run();

expect(command.template.name, 'core');
expect(result, equals(ExitCode.success.code));

verify(() => logger.progress('Bootstrapping')).called(1);
verify(
() => hooks.preGen(
vars: <String, dynamic>{
group('templates', () {
test('core', () async {
await testMultiTemplateCommand(
multiTemplatesCommand: CreateFlutterApp(
analytics: analytics,
logger: logger,
generatorFromBundle: (_) async => throw Exception('oops'),
generatorFromBrick: (_) async => generator,
),
logger: logger,
hooks: hooks,
generator: generator,
templateName: 'core',
mockArgs: {'application-id': 'xyz.app.my_app'},
expectedVars: {
'project_name': 'my_app',
'description': '',
'org_name': 'com.example.verygoodcore',
'application_id': 'xyz.app.my_app',
},
onVarsChanged: any(named: 'onVarsChanged'),
),
);
verify(
() => generator.generate(
any(),
vars: <String, dynamic>{
expectedLogSummary: 'Created a Very Good App! 🦄',
);
});

test('wear', () async {
await testMultiTemplateCommand(
multiTemplatesCommand: CreateFlutterApp(
analytics: analytics,
logger: logger,
generatorFromBundle: (_) async => throw Exception('oops'),
generatorFromBrick: (_) async => generator,
),
logger: logger,
hooks: hooks,
generator: generator,
templateName: 'wear',
mockArgs: {
'application-id': 'xyz.app.my_wear_app',
},
expectedVars: {
'project_name': 'my_app',
'description': '',
'org_name': 'com.example.verygoodcore',
'application_id': 'xyz.app.my_app',
'application_id': 'xyz.app.my_wear_app',
},
logger: logger,
),
).called(1);
expect(
progressLogs,
equals(['Generated ${generatedFiles.length} file(s)']),
);
verify(
() => logger.info('Created a Very Good App! 🦄'),
).called(1);
expectedLogSummary: 'Created a Very Good Wear OS app! ⌚️🦄',
);
});
});
});
});
Expand Down
6 changes: 0 additions & 6 deletions test/src/commands/create/create_subcommand_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ class _TestCreateSubCommandWithPublishable extends _TestCreateSubCommand
class _TestCreateSubCommandMultiTemplate extends CreateSubCommand
with MultiTemplates {
_TestCreateSubCommandMultiTemplate({
required this.defaultTemplateName,
required this.templates,
required super.analytics,
required super.logger,
Expand All @@ -88,9 +87,6 @@ class _TestCreateSubCommandMultiTemplate extends CreateSubCommand
@override
final String description = 'Create command';

@override
final String defaultTemplateName;

@override
final List<Template> templates;
}
Expand Down Expand Up @@ -932,7 +928,6 @@ Run "runner help" to see global options.''';
group('can be instantiated', () {
test('with default options', () {
final command = _TestCreateSubCommandMultiTemplate(
defaultTemplateName: 'template1',
templates: templates,
analytics: analytics,
logger: logger,
Expand Down Expand Up @@ -1014,7 +1009,6 @@ Run "runner help" to see global options.''';
});

final command = _TestCreateSubCommandMultiTemplate(
defaultTemplateName: 'template1',
templates: templates,
analytics: analytics,
logger: logger,
Expand Down
1 change: 1 addition & 0 deletions tool/generate_bundles.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ bricks=(
very_good_flutter_plugin
very_good_flame_game
very_good_docs_site
very_good_wear_app
)

for brick in "${bricks[@]}"
Expand Down