Skip to content

Commit 79f8c35

Browse files
committed
feat: create flutter package new usage
1 parent 35b9bd1 commit 79f8c35

File tree

9 files changed

+343
-9
lines changed

9 files changed

+343
-9
lines changed

.github/workflows/very_good_cli.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ jobs:
5656
# E2E tests for the create command
5757
- test/src/commands/create/e2e/flutter_app/core_test.dart
5858
- test/src/commands/create/e2e/dart_package/dart_pkg_test.dart
59+
- test/src/commands/create/e2e/flutter_package/flutter_pkg_test.dart
5960
- test/src/commands/create/e2e/dart_cli/dart_cli_test.dart
6061
- test/src/commands/create/e2e/docs_site/docs_site_test.dart
6162

lib/src/commands/create/commands/commands.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ export 'dart_cli.dart';
33
export 'dart_package.dart';
44
export 'docs_site.dart';
55
export 'flutter_app.dart';
6+
export 'flutter_package.dart';
67
export 'legacy.dart';

lib/src/commands/create/commands/create_subcommand.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ typedef MasonGeneratorFromBrick = Future<MasonGenerator> Function(Brick);
4444
///
4545
/// For sub commands that receive an org name, sub classes must mix with
4646
/// [OrgName].
47+
///
48+
/// For sub commands that receive a publishable flag, sub classes must mix with
49+
/// [Publishable].
4750
abstract class CreateSubCommand extends Command<int> {
4851
/// {@macro create_subcommand}
4952
CreateSubCommand({
@@ -235,6 +238,7 @@ abstract class CreateSubCommand extends Command<int> {
235238
/// parameters.
236239
///
237240
/// For subcommands that mix with [OrgName], it includes 'org_name'.
241+
/// For subcommands that mix with [Publishable], it includes 'publishable'.
238242
@mustCallSuper
239243
Map<String, dynamic> getTemplateVars() {
240244
final projectName = this.projectName;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import 'package:mason_logger/mason_logger.dart';
2+
import 'package:usage/usage.dart';
3+
import 'package:very_good_cli/src/commands/commands.dart';
4+
import 'package:very_good_cli/src/commands/create/templates/templates.dart';
5+
6+
/// {@template very_good_create_flutter_package_command}
7+
/// A [CreateSubCommand] for creating Flutter packages.
8+
/// {@endtemplate}
9+
class CreateFlutterPackage extends CreateSubCommand with Publishable {
10+
/// {@macro very_good_create_flutter_package_command}
11+
CreateFlutterPackage({
12+
required Analytics analytics,
13+
required Logger logger,
14+
required MasonGeneratorFromBundle? generatorFromBundle,
15+
required MasonGeneratorFromBrick? generatorFromBrick,
16+
}) : super(
17+
analytics: analytics,
18+
logger: logger,
19+
generatorFromBundle: generatorFromBundle,
20+
generatorFromBrick: generatorFromBrick,
21+
);
22+
23+
@override
24+
String get name => 'flutter_package';
25+
26+
@override
27+
List<String> get aliases => ['flutter_pkg'];
28+
29+
@override
30+
String get description =>
31+
'Creates a new very good Flutter package in the specified directory.';
32+
33+
@override
34+
Template get template => FlutterPkgTemplate();
35+
}

lib/src/commands/create/create.dart

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class CreateCommand extends Command<int> {
3939
),
4040
);
4141

42-
// very_good create dart_pkg <args>
42+
// very_good create dart_package <args>
4343
addSubcommand(
4444
CreateDartPackage(
4545
analytics: analytics,
@@ -68,6 +68,16 @@ class CreateCommand extends Command<int> {
6868
generatorFromBrick: generatorFromBrick,
6969
),
7070
);
71+
72+
// very_good create flutter_package <args>
73+
addSubcommand(
74+
CreateFlutterPackage(
75+
analytics: analytics,
76+
logger: logger,
77+
generatorFromBundle: generatorFromBundle,
78+
generatorFromBrick: generatorFromBrick,
79+
),
80+
);
7181
}
7282

7383
@override
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
import 'dart:io';
2+
3+
import 'package:args/args.dart';
4+
import 'package:mason/mason.dart';
5+
import 'package:mocktail/mocktail.dart';
6+
import 'package:path/path.dart' as path;
7+
import 'package:test/test.dart';
8+
import 'package:usage/usage.dart';
9+
import 'package:very_good_cli/src/commands/commands.dart';
10+
11+
import '../../../../helpers/helpers.dart';
12+
13+
class MockAnalytics extends Mock implements Analytics {}
14+
15+
class MockLogger extends Mock implements Logger {}
16+
17+
class MockMasonGenerator extends Mock implements MasonGenerator {}
18+
19+
class MockGeneratorHooks extends Mock implements GeneratorHooks {}
20+
21+
class MockArgResults extends Mock implements ArgResults {}
22+
23+
class FakeLogger extends Fake implements Logger {}
24+
25+
class FakeDirectoryGeneratorTarget extends Fake
26+
implements DirectoryGeneratorTarget {}
27+
28+
final expectedUsage = [
29+
'''
30+
Creates a new very good Flutter package in the specified directory.
31+
32+
Usage: very_good create flutter_package <project-name> [arguments]
33+
-h, --help Print this usage information.
34+
-o, --output-directory The desired output directory when creating a new project.
35+
--description The description for this new project.
36+
(defaults to "A Very Good Project created by Very Good CLI.")
37+
--publishable Whether the generated project is intended to be published.
38+
39+
Run "very_good help" to see global options.''',
40+
];
41+
42+
const pubspec = '''
43+
name: example
44+
environment:
45+
sdk: ">=2.13.0 <3.0.0"
46+
''';
47+
48+
void main() {
49+
late Analytics analytics;
50+
late Logger logger;
51+
52+
setUpAll(() {
53+
registerFallbackValue(FakeDirectoryGeneratorTarget());
54+
registerFallbackValue(FakeLogger());
55+
});
56+
57+
setUp(() {
58+
analytics = MockAnalytics();
59+
when(
60+
() => analytics.sendEvent(any(), any(), label: any(named: 'label')),
61+
).thenAnswer((_) async {});
62+
when(
63+
() => analytics.waitForLastPing(timeout: any(named: 'timeout')),
64+
).thenAnswer((_) async {});
65+
66+
logger = MockLogger();
67+
68+
final progress = MockProgress();
69+
70+
when(() => logger.progress(any())).thenReturn(progress);
71+
});
72+
73+
group('can be instantiated', () {
74+
test('with default options', () {
75+
final logger = Logger();
76+
final command = CreateFlutterPackage(
77+
analytics: analytics,
78+
logger: logger,
79+
generatorFromBundle: null,
80+
generatorFromBrick: null,
81+
);
82+
expect(command.name, equals('flutter_package'));
83+
expect(
84+
command.description,
85+
equals(
86+
'Creates a new very good Flutter package in the specified directory.',
87+
),
88+
);
89+
expect(command.logger, equals(logger));
90+
expect(command, isA<Publishable>());
91+
});
92+
});
93+
94+
group('create flutter_package', () {
95+
test(
96+
'help',
97+
withRunner((commandRunner, logger, pubUpdater, printLogs) async {
98+
final result =
99+
await commandRunner.run(['create', 'flutter_package', '--help']);
100+
expect(printLogs, equals(expectedUsage));
101+
expect(result, equals(ExitCode.success.code));
102+
103+
printLogs.clear();
104+
105+
final resultAbbr =
106+
await commandRunner.run(['create', 'flutter_pkg', '-h']);
107+
expect(printLogs, equals(expectedUsage));
108+
expect(resultAbbr, equals(ExitCode.success.code));
109+
}),
110+
);
111+
112+
group('running the command', () {
113+
final generatedFiles =
114+
List.filled(10, const GeneratedFile.created(path: ''));
115+
116+
late GeneratorHooks hooks;
117+
late MasonGenerator generator;
118+
119+
setUp(() {
120+
hooks = MockGeneratorHooks();
121+
generator = MockMasonGenerator();
122+
123+
when(() => generator.hooks).thenReturn(hooks);
124+
when(
125+
() => hooks.preGen(
126+
vars: any(named: 'vars'),
127+
onVarsChanged: any(named: 'onVarsChanged'),
128+
),
129+
).thenAnswer((_) async {});
130+
131+
when(
132+
() => generator.generate(
133+
any(),
134+
vars: any(named: 'vars'),
135+
logger: any(named: 'logger'),
136+
),
137+
).thenAnswer((_) async {
138+
return generatedFiles;
139+
});
140+
141+
when(() => generator.id).thenReturn('generator_id');
142+
when(() => generator.description).thenReturn('generator description');
143+
when(() => generator.hooks).thenReturn(hooks);
144+
145+
when(
146+
() => hooks.preGen(
147+
vars: any(named: 'vars'),
148+
onVarsChanged: any(named: 'onVarsChanged'),
149+
),
150+
).thenAnswer((_) async {});
151+
when(
152+
() => generator.generate(
153+
any(),
154+
vars: any(named: 'vars'),
155+
logger: any(named: 'logger'),
156+
),
157+
).thenAnswer((_) async {
158+
final target =
159+
_.positionalArguments.first as DirectoryGeneratorTarget;
160+
File(path.join(target.dir.path, 'my_flutter_package', 'pubspec.yaml'))
161+
..createSync(recursive: true)
162+
..writeAsStringSync(pubspec);
163+
return generatedFiles;
164+
});
165+
});
166+
167+
test('creates flutter package', () async {
168+
final tempDir = Directory.systemTemp.createTempSync();
169+
addTearDown(() => tempDir.deleteSync(recursive: true));
170+
final argResults = MockArgResults();
171+
final command = CreateFlutterPackage(
172+
analytics: analytics,
173+
logger: logger,
174+
generatorFromBundle: (_) async => throw Exception('oops'),
175+
generatorFromBrick: (_) async => generator,
176+
)..argResultOverrides = argResults;
177+
when(() => argResults['output-directory'] as String?)
178+
.thenReturn(tempDir.path);
179+
when(() => argResults.rest).thenReturn(['my_flutter_package']);
180+
181+
final result = await command.run();
182+
183+
expect(command.template.name, 'flutter_pkg');
184+
expect(result, equals(ExitCode.success.code));
185+
186+
verify(() => logger.progress('Bootstrapping')).called(1);
187+
verify(
188+
() => hooks.preGen(
189+
vars: <String, dynamic>{
190+
'project_name': 'my_flutter_package',
191+
'description': '',
192+
'publishable': false,
193+
},
194+
onVarsChanged: any(named: 'onVarsChanged'),
195+
),
196+
);
197+
verify(
198+
() => generator.generate(
199+
any(),
200+
vars: <String, dynamic>{
201+
'project_name': 'my_flutter_package',
202+
'description': '',
203+
'publishable': false,
204+
},
205+
logger: logger,
206+
),
207+
).called(1);
208+
verify(
209+
() => logger.info('Created a Very Good Flutter Package! 🦄'),
210+
).called(1);
211+
});
212+
});
213+
});
214+
}

test/src/commands/create/commands/legacy_test.dart

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ Usage: very_good create <subcommand> <project-name> [arguments]
2323
-h, --help Print this usage information.
2424
2525
Available subcommands:
26-
dart_cli Creates a new very good Dart CLI in the specified directory.
27-
dart_package Creates a new very good Dart package in the specified directory.
28-
docs_site Creates a new very good docs site in the specified directory.
29-
flutter_app Creates a new very good Flutter app in the specified directory.
26+
dart_cli Creates a new very good Dart CLI in the specified directory.
27+
dart_package Creates a new very good Dart package in the specified directory.
28+
docs_site Creates a new very good docs site in the specified directory.
29+
flutter_app Creates a new very good Flutter app in the specified directory.
30+
flutter_package Creates a new very good Flutter package in the specified directory.
3031
3132
Run "very_good help" to see global options.'''
3233
];

test/src/commands/create/create_test.dart

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ Usage: very_good create <subcommand> <project-name> [arguments]
2929
-h, --help Print this usage information.
3030
3131
Available subcommands:
32-
dart_cli Creates a new very good Dart CLI in the specified directory.
33-
dart_package Creates a new very good Dart package in the specified directory.
34-
docs_site Creates a new very good docs site in the specified directory.
35-
flutter_app Creates a new very good Flutter app in the specified directory.
32+
dart_cli Creates a new very good Dart CLI in the specified directory.
33+
dart_package Creates a new very good Dart package in the specified directory.
34+
docs_site Creates a new very good docs site in the specified directory.
35+
flutter_app Creates a new very good Flutter app in the specified directory.
36+
flutter_package Creates a new very good Flutter package in the specified directory.
3637
3738
Run "very_good help" to see global options.'''
3839
];

0 commit comments

Comments
 (0)