From 1d191919750d01918085197dcdbe3c6aedac4d81 Mon Sep 17 00:00:00 2001 From: Marcos Sevilla Date: Thu, 29 Oct 2026 12:16:42 +0100 Subject: [PATCH] test(create): fail when template bundle vars drift from the CLI Template bundles are regenerated by automated bump PRs, so new bundle vars could land without the CLI passing them. Add a unit test that checks every create subcommand's templateVarResolvers against its templates' bundle vars in both directions, plus Template.defaultedVars as the explicit allowlist for vars intentionally left to the template default. flame_game's application_id is allowlisted since the template derives it from org_name and project_name. flutter_plugin now passes dart_fix_output: true; the var is only read by the post_gen hook, which the CLI never runs, so generated output is unchanged. This check fails on purpose for the org_name var of app_ui_package, dart_cli, dart_package and flutter_package until the follow-up that adds --org-name to those subcommands. --- .../create/commands/flutter_plugin.dart | 1 + .../commands/create/templates/template.dart | 6 ++ .../very_good_flame_game_template.dart | 5 ++ .../create/commands/flutter_plugin_test.dart | 2 + .../commands/create/template_vars_test.dart | 68 +++++++++++++++++++ 5 files changed, 82 insertions(+) create mode 100644 test/src/commands/create/template_vars_test.dart diff --git a/lib/src/commands/create/commands/flutter_plugin.dart b/lib/src/commands/create/commands/flutter_plugin.dart index 69862f82b..b80d940f9 100644 --- a/lib/src/commands/create/commands/flutter_plugin.dart +++ b/lib/src/commands/create/commands/flutter_plugin.dart @@ -41,5 +41,6 @@ class CreateFlutterPlugin({ TemplateVarResolvers get templateVarResolvers => { ...super.templateVarResolvers, 'platforms': () => argResults['platforms'] as List, + 'dart_fix_output': () => true, }; } diff --git a/lib/src/commands/create/templates/template.dart b/lib/src/commands/create/templates/template.dart index ae3d1a7f8..fe5ec6ddd 100644 --- a/lib/src/commands/create/templates/template.dart +++ b/lib/src/commands/create/templates/template.dart @@ -19,6 +19,12 @@ abstract class const Template({ /// {@macro template} this; + /// The [bundle] vars intentionally left to the template's default value. + /// + /// Every bundle var must either be passed by the `very_good create` + /// subcommand that uses this template or be listed here. + Set get defaultedVars => const {}; + /// Callback invoked after template generation has completed. Future onGenerateComplete(Logger logger, Directory outputDir); } diff --git a/lib/src/commands/create/templates/very_good_flame_game/very_good_flame_game_template.dart b/lib/src/commands/create/templates/very_good_flame_game/very_good_flame_game_template.dart index d179bda83..0d4860985 100644 --- a/lib/src/commands/create/templates/very_good_flame_game/very_good_flame_game_template.dart +++ b/lib/src/commands/create/templates/very_good_flame_game/very_good_flame_game_template.dart @@ -16,6 +16,11 @@ class VeryGoodFlameGameTemplate() extends Template { help: 'Generate a Very Good Flame game.', ); + /// `application_id` is derived from `org_name` and `project_name` by the + /// template when omitted. + @override + Set get defaultedVars => const {'application_id'}; + @override Future onGenerateComplete(Logger logger, Directory outputDir) async { if (await installFlutterPackages(logger, outputDir)) { diff --git a/test/src/commands/create/commands/flutter_plugin_test.dart b/test/src/commands/create/commands/flutter_plugin_test.dart index c1a6fb192..7f18703f4 100644 --- a/test/src/commands/create/commands/flutter_plugin_test.dart +++ b/test/src/commands/create/commands/flutter_plugin_test.dart @@ -219,6 +219,7 @@ void main() { 'publishable': false, 'workspace': false, 'platforms': ['android', 'ios', 'windows'], + 'dart_fix_output': true, }, onVarsChanged: any(named: 'onVarsChanged'), ), @@ -233,6 +234,7 @@ void main() { 'publishable': false, 'workspace': false, 'platforms': ['android', 'ios', 'windows'], + 'dart_fix_output': true, }, logger: logger, ), diff --git a/test/src/commands/create/template_vars_test.dart b/test/src/commands/create/template_vars_test.dart new file mode 100644 index 000000000..799e1056f --- /dev/null +++ b/test/src/commands/create/template_vars_test.dart @@ -0,0 +1,68 @@ +import 'package:mason/mason.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; +import 'package:very_good_cli/src/commands/create/commands/commands.dart'; +import 'package:very_good_cli/src/commands/create/create.dart'; + +class _MockLogger extends Mock implements Logger; + +void main() { + group('template vars', () { + final subcommands = CreateCommand(logger: _MockLogger()).subcommands.values + .toSet() + .cast(); + + for (final subcommand in subcommands) { + final templates = switch (subcommand) { + MultiTemplates(:final templates) => templates, + _ => [subcommand.template], + }; + + for (final template in templates) { + final bundle = template.bundle; + final bundleVars = bundle.vars.keys.toSet(); + final handledVars = subcommand.templateVarResolvers.keys.toSet(); + final defaultedVars = template.defaultedVars; + final source = + 'very_good create ${subcommand.name} ' + '(template "${template.name}", bundle "${bundle.name}")'; + + group('${subcommand.name} ${template.name}', () { + test('handles every bundle var', () { + expect( + bundleVars.difference(handledVars).difference(defaultedVars), + isEmpty, + reason: + '$source does not pass these bundle vars. Add them to ' + "${subcommand.runtimeType}'s templateVarResolvers, or to " + "${template.runtimeType}'s defaultedVars to keep the " + 'template default.', + ); + }); + + test('only passes vars declared by the bundle', () { + expect( + handledVars.difference(bundleVars), + isEmpty, + reason: + '$source passes these vars, but the bundle does not ' + 'declare them. Remove them from ' + "${subcommand.runtimeType}'s templateVarResolvers.", + ); + }); + + test('only defaults vars declared by the bundle', () { + expect( + defaultedVars.difference(bundleVars), + isEmpty, + reason: + '$source defaults these vars, but the bundle does not ' + 'declare them. Remove them from ' + "${template.runtimeType}'s defaultedVars.", + ); + }); + }); + } + } + }); +}