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
26 changes: 26 additions & 0 deletions lib/src/commands/create/commands/app_ui_package.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import 'package:very_good_cli/src/commands/commands.dart';
import 'package:very_good_cli/src/commands/create/templates/templates.dart';

/// {@template very_good_create_app_ui_package_command}
/// A [CreateSubCommand] for creating App UI packages.
/// {@endtemplate}
class CreateAppUiPackage extends CreateSubCommand with Publishable {
/// {@macro very_good_create_app_ui_package_command}
CreateAppUiPackage({
required super.logger,
required super.generatorFromBundle,
required super.generatorFromBrick,
});

@override
String get name => 'app_ui_package';

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

@override
String get description => 'Generate a Very Good App UI package.';

@override
Template get template => AppUiTemplate();
}
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 'app_ui_package.dart';
export 'create_subcommand.dart';
export 'dart_cli.dart';
export 'dart_package.dart';
Expand Down
9 changes: 9 additions & 0 deletions lib/src/commands/create/create.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ class CreateCommand extends Command<int> {
@visibleForTesting MasonGeneratorFromBundle? generatorFromBundle,
@visibleForTesting MasonGeneratorFromBrick? generatorFromBrick,
}) {
// very_good create app_ui_package <args>
addSubcommand(
CreateAppUiPackage(
logger: logger,
generatorFromBundle: generatorFromBundle,
generatorFromBrick: generatorFromBrick,
),
);

// very_good create flutter_app <args>
addSubcommand(
CreateFlutterApp(
Expand Down
1 change: 1 addition & 0 deletions lib/src/commands/create/templates/templates.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export 'post_generate_actions.dart';
export 'template.dart';
export 'very_good_app_ui/very_good_app_ui.dart';
export 'very_good_core/very_good_core.dart';
export 'very_good_dart_cli/very_good_dart_cli.dart';
export 'very_good_dart_package/very_good_dart_package.dart';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export 'very_good_app_ui_bundle.dart';
export 'very_good_app_ui_template.dart';

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'package:mason/mason.dart';
import 'package:universal_io/io.dart';
import 'package:very_good_cli/src/commands/create/templates/templates.dart';
import 'package:very_good_cli/src/logger_extension.dart';

/// {@template app_ui_template}
/// A Flutter app UI package template.
/// {@endtemplate}
class AppUiTemplate extends Template {
/// {@macro app_ui_template}
AppUiTemplate()
: super(
name: 'app_ui',
bundle: veryGoodAppUiBundle,
help: 'Generate a Very Good App UI package.',
);

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

void _logSummary(Logger logger) {
logger
..info('\n')
..created('Created a Very Good App UI Package! 🦄')
..info('\n');
}
}
210 changes: 210 additions & 0 deletions test/src/commands/create/commands/app_ui_package_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
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:very_good_cli/src/commands/commands.dart';

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

class _MockLogger extends Mock implements Logger {}

class _MockProgress extends Mock implements Progress {}

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 = [
'''
Generate a Very Good App UI package.

Usage: very_good create app_ui_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: ^3.11.0
''';

void main() {
late Logger logger;

setUpAll(() {
registerFallbackValue(_FakeDirectoryGeneratorTarget());
registerFallbackValue(_FakeLogger());
});

setUp(() {
logger = _MockLogger();

final progress = _MockProgress();

when(() => logger.progress(any())).thenReturn(progress);
});

group('can be instantiated', () {
test('with default options', () {
final logger = Logger();
final command = CreateAppUiPackage(
logger: logger,
generatorFromBundle: null,
generatorFromBrick: null,
);
expect(command.name, equals('app_ui_package'));
expect(
command.description,
equals('Generate a Very Good App UI package.'),
);
expect(command.logger, equals(logger));
expect(command, isA<Publishable>());
});
});

group('create app_ui_package', () {
test(
'help',
withRunner((commandRunner, logger, pubUpdater, printLogs) async {
final result = await commandRunner.run([
'create',
'app_ui_package',
'--help',
]);
expect(printLogs, equals(expectedUsage));
expect(result, equals(ExitCode.success.code));

printLogs.clear();

final resultAbbr = await commandRunner.run([
'create',
'app_ui_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((invocation) async {
final target =
invocation.positionalArguments.first as DirectoryGeneratorTarget;
File(path.join(target.dir.path, 'pubspec.yaml'))
..createSync(recursive: true)
..writeAsStringSync(pubspec);
return generatedFiles;
});
});

test('creates app ui package', () async {
final tempDirectory = Directory.systemTemp.createTempSync();
addTearDown(() => tempDirectory.deleteSync(recursive: true));

final argResults = _MockArgResults();
final command = CreateAppUiPackage(
logger: logger,
generatorFromBundle: (_) async => throw Exception('oops'),
generatorFromBrick: (_) async => generator,
)..argResultOverrides = argResults;
when(
() => argResults['output-directory'] as String?,
).thenReturn(tempDirectory.path);
when(() => argResults.rest).thenReturn(['my_app_ui']);

final result = await command.run();

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

verify(() => logger.progress('Bootstrapping')).called(1);
verify(
() => hooks.preGen(
vars: <String, dynamic>{
'project_name': 'my_app_ui',
'description': '',
'publishable': false,
},
onVarsChanged: any(named: 'onVarsChanged'),
),
);
verify(
() => generator.generate(
any(),
vars: <String, dynamic>{
'project_name': 'my_app_ui',
'description': '',
'publishable': false,
},
logger: logger,
),
).called(1);
verify(
() => logger.info('Created a Very Good App UI Package! 🦄'),
).called(1);
});
});
});
}
1 change: 1 addition & 0 deletions test/src/commands/create/create_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Usage: very_good create <subcommand> <project-name> [arguments]
-h, --help Print this usage information.

Available subcommands:
app_ui_package Generate a Very Good App UI package.
dart_cli Generate a Very Good Dart CLI application.
dart_package Generate a Very Good Dart package.
docs_site Generate a Very Good documentation site.
Expand Down
1 change: 1 addition & 0 deletions tool/generate_bundles.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Runs `mason bundle` to generate bundles for all bricks within the respective templates directories.

bricks=(
very_good_app_ui
very_good_core
very_good_dart_package
very_good_dart_cli
Expand Down