From 38ab4271af269becf3456644e9179e6d5f2c5408 Mon Sep 17 00:00:00 2001 From: Sang Nguyen Date: Sun, 4 May 2025 10:31:14 -0600 Subject: [PATCH 1/7] format --- .vscode/settings.json | 3 ++ build.yaml | 3 +- example/bdd_ignore.yaml | 3 ++ example/lib/main.dart | 4 +- example/pubspec.lock | 2 +- .../calculator/calculator.bdd_scenarios.dart | 5 --- .../test/calculator/calculator.bdd_test.dart | 18 ++++---- ...rios.dart => counter.bdd_scenarios.g.dart} | 3 -- example/test/counter/counter.bdd_test.dart | 23 ---------- example/test/counter/counter.bdd_test.g.dart | 23 ++++++++++ ...arios.dart => sample.bdd_scenarios.g.dart} | 5 --- ...e.bdd_test.dart => sample.bdd_test.g.dart} | 38 ++++++++-------- lib/bdd_flutter.dart | 13 ++++++ lib/src/builder.dart | 18 +++++++- lib/src/extensions/string_x.dart | 9 +--- .../bdd_builders/bdd_feature_builder.dart | 44 ++++++------------- .../bdd_builders/bdd_test_file_builder.dart | 16 +++---- .../bdd_builders/scenario_file_builder.dart | 2 +- .../feature/builder/domain/bdd_ignore.dart | 27 ++++++++++++ lib/src/feature/builder/domain/decorator.dart | 15 +++---- lib/src/feature/report/test_reporter.dart | 25 +++-------- pubspec.yaml | 6 ++- test/feature_builder/bdd_flutter_test.dart | 3 +- 23 files changed, 156 insertions(+), 152 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 example/bdd_ignore.yaml rename example/test/counter/{counter.bdd_scenarios.dart => counter.bdd_scenarios.g.dart} (99%) delete mode 100644 example/test/counter/counter.bdd_test.dart create mode 100644 example/test/counter/counter.bdd_test.g.dart rename example/test/sample/{sample.bdd_scenarios.dart => sample.bdd_scenarios.g.dart} (99%) rename example/test/sample/{sample.bdd_test.dart => sample.bdd_test.g.dart} (55%) create mode 100644 lib/src/feature/builder/domain/bdd_ignore.dart diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c5d18d2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "dart.lineLength": 200 +} \ No newline at end of file diff --git a/build.yaml b/build.yaml index 0a1dd89..9b65fee 100644 --- a/build.yaml +++ b/build.yaml @@ -2,6 +2,7 @@ builders: bdd_flutter|bdd_test_builder: import: "package:bdd_flutter/bdd_flutter.dart" builder_factories: ["bddTestBuilder"] - build_extensions: { ".feature": [".bdd_scenarios.dart", ".bdd_test.dart"] } + build_extensions: + { ".feature": [".bdd_scenarios.g.dart", ".bdd_test.g.dart"] } auto_apply: dependents build_to: source diff --git a/example/bdd_ignore.yaml b/example/bdd_ignore.yaml new file mode 100644 index 0000000..1bbdd28 --- /dev/null +++ b/example/bdd_ignore.yaml @@ -0,0 +1,3 @@ +ignore_files: + - test/sample/sample.bdd_scenarios.dart + - test/sample/sample.bdd_test.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 640824f..2afe9b9 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -9,8 +9,6 @@ class MainApp extends StatelessWidget { @override Widget build(BuildContext context) { - return const MaterialApp( - home: Scaffold(body: Center(child: Text('Hello World!'))), - ); + return const MaterialApp(home: Scaffold(body: Center(child: Text('Hello World!')))); } } diff --git a/example/pubspec.lock b/example/pubspec.lock index 9cc5ab2..bb999fb 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -44,7 +44,7 @@ packages: path: ".." relative: true source: path - version: "0.1.5" + version: "0.2.0" boolean_selector: dependency: transitive description: diff --git a/example/test/calculator/calculator.bdd_scenarios.dart b/example/test/calculator/calculator.bdd_scenarios.dart index 66e5be3..12fad21 100644 --- a/example/test/calculator/calculator.bdd_scenarios.dart +++ b/example/test/calculator/calculator.bdd_scenarios.dart @@ -12,7 +12,6 @@ class AddTwoNumbersScenario { static Future theResultShouldBe3(WidgetTester tester) async { // TODO: Implement Then the result should be 3 } - } class SubtractTwoNumbersScenario { @@ -27,7 +26,6 @@ class SubtractTwoNumbersScenario { static Future theResultShouldBe2(WidgetTester tester) async { // TODO: Implement Then the result should be 2 } - } class MultiplyTwoNumbersScenario { @@ -42,7 +40,6 @@ class MultiplyTwoNumbersScenario { static Future theResultShouldBe6(WidgetTester tester) async { // TODO: Implement Then the result should be 6 } - } class DivideTwoNumbersScenario { @@ -57,6 +54,4 @@ class DivideTwoNumbersScenario { static Future theResultShouldBe(WidgetTester tester, String result) async { // TODO: Implement Then the result should be } - } - diff --git a/example/test/calculator/calculator.bdd_test.dart b/example/test/calculator/calculator.bdd_test.dart index 6c367fb..b23e37b 100644 --- a/example/test/calculator/calculator.bdd_test.dart +++ b/example/test/calculator/calculator.bdd_test.dart @@ -33,17 +33,17 @@ void main() { testWidgets('Divide two numbers', (tester) async { //Scenario: Divide two numbers final examples = [ - {'number1': '10','number2': '2','result': '5',}, - {'number1': '10','number2': '1','result': '10',}, - {'number1': '10','number2': '10','result': '1',}, + {'number1': '10', 'number2': '2', 'result': '5'}, + {'number1': '10', 'number2': '1', 'result': '10'}, + {'number1': '10', 'number2': '10', 'result': '1'}, ]; for (var example in examples) { - // Given I have the number - await DivideTwoNumbersScenario.iHaveTheNumber(tester, example['number1']!); - // When I divide them - await DivideTwoNumbersScenario.iDivideThem(tester); - // Then the result should be - await DivideTwoNumbersScenario.theResultShouldBe(tester, example['result']!); + // Given I have the number + await DivideTwoNumbersScenario.iHaveTheNumber(tester, example['number1']!); + // When I divide them + await DivideTwoNumbersScenario.iDivideThem(tester); + // Then the result should be + await DivideTwoNumbersScenario.theResultShouldBe(tester, example['result']!); } }); }); diff --git a/example/test/counter/counter.bdd_scenarios.dart b/example/test/counter/counter.bdd_scenarios.g.dart similarity index 99% rename from example/test/counter/counter.bdd_scenarios.dart rename to example/test/counter/counter.bdd_scenarios.g.dart index eaeabd3..cd8095f 100644 --- a/example/test/counter/counter.bdd_scenarios.dart +++ b/example/test/counter/counter.bdd_scenarios.g.dart @@ -4,7 +4,6 @@ class CounterBackground { static Future iHaveACounterWithValue0() async { // TODO: Implement Given I have a counter with value 0 } - } class IncrementScenario { @@ -15,6 +14,4 @@ class IncrementScenario { static Future theCounterShouldHaveValue(WidgetTester tester, String expectedvalue) async { // TODO: Implement Then the counter should have value } - } - diff --git a/example/test/counter/counter.bdd_test.dart b/example/test/counter/counter.bdd_test.dart deleted file mode 100644 index fe19468..0000000 --- a/example/test/counter/counter.bdd_test.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'counter.bdd_scenarios.dart'; - -void main() { - group('Counter', () { - //Background: I have a counter with value 0 - CounterBackground.iHaveACounterWithValue0(); - testWidgets('Increment', (tester) async { - //Scenario: Increment - final examples = [ - {'value': '1','expectedvalue': '1',}, - {'value': '2','expectedvalue': '2',}, - {'value': '3','expectedvalue': '3',}, - ]; - for (var example in examples) { - // When I increment the counter by - await IncrementScenario.iIncrementTheCounterBy(tester, example['value']!); - // Then the counter should have value - await IncrementScenario.theCounterShouldHaveValue(tester, example['expected_value']!); - } - }); - }); -} diff --git a/example/test/counter/counter.bdd_test.g.dart b/example/test/counter/counter.bdd_test.g.dart new file mode 100644 index 0000000..54c263c --- /dev/null +++ b/example/test/counter/counter.bdd_test.g.dart @@ -0,0 +1,23 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'counter.bdd_scenarios.g.dart'; + +void main() { + group('Counter', () { + //Background: I have a counter with value 0 + CounterBackground.iHaveACounterWithValue0(); + testWidgets('Increment', (tester) async { + //Scenario: Increment + final examples = [ + {'value': '1', 'expectedvalue': '1'}, + {'value': '2', 'expectedvalue': '2'}, + {'value': '3', 'expectedvalue': '3'}, + ]; + for (var example in examples) { + // When I increment the counter by + await IncrementScenario.iIncrementTheCounterBy(tester, example['value']!); + // Then the counter should have value + await IncrementScenario.theCounterShouldHaveValue(tester, example['expected_value']!); + } + }); + }); +} diff --git a/example/test/sample/sample.bdd_scenarios.dart b/example/test/sample/sample.bdd_scenarios.g.dart similarity index 99% rename from example/test/sample/sample.bdd_scenarios.dart rename to example/test/sample/sample.bdd_scenarios.g.dart index 0b8db30..b75acf7 100644 --- a/example/test/sample/sample.bdd_scenarios.dart +++ b/example/test/sample/sample.bdd_scenarios.g.dart @@ -12,7 +12,6 @@ class SampleScenario { static Future iShouldSeeTheSampleFeature(WidgetTester tester) async { // TODO: Implement Then I should see the sample feature } - } class CounterScenario { @@ -27,7 +26,6 @@ class CounterScenario { static Future iShouldSeeTheCounterIncremented(WidgetTester tester) async { // TODO: Implement Then I should see the counter incremented } - } class CounterWithExamplesScenario { @@ -42,7 +40,6 @@ class CounterWithExamplesScenario { static Future iShouldSeeTheCounterIncremented(WidgetTester tester) async { // TODO: Implement Then I should see the counter incremented } - } class CounterWithParametersScenario { @@ -57,6 +54,4 @@ class CounterWithParametersScenario { static Future iShouldSeeTheResult(WidgetTester tester, String result) async { // TODO: Implement Then I should see the result } - } - diff --git a/example/test/sample/sample.bdd_test.dart b/example/test/sample/sample.bdd_test.g.dart similarity index 55% rename from example/test/sample/sample.bdd_test.dart rename to example/test/sample/sample.bdd_test.g.dart index 09a4c18..e47642e 100644 --- a/example/test/sample/sample.bdd_test.dart +++ b/example/test/sample/sample.bdd_test.g.dart @@ -1,5 +1,5 @@ import 'package:flutter_test/flutter_test.dart'; -import 'sample.bdd_scenarios.dart'; +import 'sample.bdd_scenarios.g.dart'; void main() { group('Sample', () { @@ -24,33 +24,33 @@ void main() { testWidgets('Counter with examples', (tester) async { //Scenario: Counter with examples final examples = [ - {'counter': '1',}, - {'counter': '2',}, - {'counter': '3',}, + {'counter': '1'}, + {'counter': '2'}, + {'counter': '3'}, ]; for (var example in examples) { - // Given I have a counter - await CounterWithExamplesScenario.iHaveACounter(tester); - // When I increment the - await CounterWithExamplesScenario.iIncrementThe(tester, example['counter']!); - // Then I should see the counter incremented - await CounterWithExamplesScenario.iShouldSeeTheCounterIncremented(tester); + // Given I have a counter + await CounterWithExamplesScenario.iHaveACounter(tester); + // When I increment the + await CounterWithExamplesScenario.iIncrementThe(tester, example['counter']!); + // Then I should see the counter incremented + await CounterWithExamplesScenario.iShouldSeeTheCounterIncremented(tester); } }); testWidgets('Counter with parameters', (tester) async { //Scenario: Counter with parameters final examples = [ - {'counter': '1','result': '2',}, - {'counter': '2','result': '3',}, - {'counter': '3','result': '4',}, + {'counter': '1', 'result': '2'}, + {'counter': '2', 'result': '3'}, + {'counter': '3', 'result': '4'}, ]; for (var example in examples) { - // Given I have a counter - await CounterWithParametersScenario.iHaveACounter(tester); - // When I increment the counter - await CounterWithParametersScenario.iIncrementTheCounter(tester, example['counter']!); - // Then I should see the result - await CounterWithParametersScenario.iShouldSeeTheResult(tester, example['result']!); + // Given I have a counter + await CounterWithParametersScenario.iHaveACounter(tester); + // When I increment the counter + await CounterWithParametersScenario.iIncrementTheCounter(tester, example['counter']!); + // Then I should see the result + await CounterWithParametersScenario.iShouldSeeTheResult(tester, example['result']!); } }); }); diff --git a/lib/bdd_flutter.dart b/lib/bdd_flutter.dart index c54e3f3..0eaff16 100644 --- a/lib/bdd_flutter.dart +++ b/lib/bdd_flutter.dart @@ -12,6 +12,7 @@ export 'src/builder.dart' show bddTestBuilder; /// * Generate test files automatically /// * Support for both widget and non-widget tests /// * Customizable test generation options +/// * Ignore specific generated files using bdd_ignore.yaml /// /// ## Getting Started /// @@ -52,6 +53,18 @@ export 'src/builder.dart' show bddTestBuilder; /// generate_widget_tests: true /// ``` /// +/// ## Ignoring Generated Files +/// +/// If you need to modify a generated test file, you can prevent it from being +/// overwritten by adding it to `bdd_ignore.yaml`: +/// ```yaml +/// ignore_files: +/// - test/features/login_test.dart +/// - test/features/registration_test.dart +/// ``` +/// +/// Files listed in `bdd_ignore.yaml` will be skipped during build_runner execution. +/// /// ## Additional Information /// /// For more information, visit the [documentation](https://example.com/bdd_flutter). diff --git a/lib/src/builder.dart b/lib/src/builder.dart index 38dec32..5871afe 100644 --- a/lib/src/builder.dart +++ b/lib/src/builder.dart @@ -1,6 +1,7 @@ // lib/builder.dart import 'dart:async'; +import 'package:bdd_flutter/src/feature/builder/domain/bdd_ignore.dart'; import 'package:bdd_flutter/src/feature/builder/domain/bdd_options.dart'; import 'package:build/build.dart'; @@ -14,15 +15,28 @@ class BDDTestBuilder implements Builder { @override final buildExtensions = const { - r'.feature': ['.bdd_scenarios.dart', '.bdd_test.dart'], + r'.feature': ['.bdd_scenarios.g.dart', '.bdd_test.g.dart'], }; @override Future build(BuildStep buildStep) async { - final factory = BDDFactory.create(options); + await BDDIgnore.initialize(); + final factory = BDDFactory.create(options); final feature = await factory.featureBuilder.build(buildStep); + // Check if we should ignore the generated files + final outputId = buildStep.inputId.changeExtension('.bdd_test.g.dart'); + + if (BDDIgnore.shouldIgnore(outputId.path)) { + return; + } + + // Skip generation if feature is empty (due to @ignore) + if (feature.name.isEmpty) { + return; + } + await factory.scenarioBuilder.build(buildStep, feature); await factory.testFileBuilder.build(buildStep, feature); } diff --git a/lib/src/extensions/string_x.dart b/lib/src/extensions/string_x.dart index ef7bd3c..4de0f71 100644 --- a/lib/src/extensions/string_x.dart +++ b/lib/src/extensions/string_x.dart @@ -14,10 +14,7 @@ extension StringX on String { } String get name { - return split(' ') - .where((word) => word.isNotEmpty) - .map((word) => word[0].toUpperCase() + word.substring(1).toLowerCase()) - .join(''); + return split(' ').where((word) => word.isNotEmpty).map((word) => word[0].toUpperCase() + word.substring(1).toLowerCase()).join(''); } String get toScenarioClassName { @@ -25,9 +22,7 @@ extension StringX on String { } String get snakeCaseToCamelCase { - return split('_') - .map((word) => word[0].toLowerCase() + word.substring(1).toLowerCase()) - .join(''); + return split('_').map((word) => word[0].toLowerCase() + word.substring(1).toLowerCase()).join(''); } String get toSnakeCase { diff --git a/lib/src/feature/builder/bdd_builders/bdd_feature_builder.dart b/lib/src/feature/builder/bdd_builders/bdd_feature_builder.dart index 22e5367..d550288 100644 --- a/lib/src/feature/builder/bdd_builders/bdd_feature_builder.dart +++ b/lib/src/feature/builder/bdd_builders/bdd_feature_builder.dart @@ -21,8 +21,7 @@ class BDDFeatureBuilder { } Feature parseFeature(String featureContent) { - final lines = - featureContent.split('\n').map((line) => line.trim()).toList(); + final lines = featureContent.split('\n').map((line) => line.trim()).toList(); String? featureName; List scenarios = []; List currentSteps = []; @@ -39,6 +38,9 @@ class BDDFeatureBuilder { if (line.startsWith('Feature:')) { featureName = line.substring('Feature:'.length).trim(); + } else if (line.startsWith('@ignore')) { + // If @ignore is found, return an empty feature to skip generation + return Feature('', []); } else if (line.startsWith('Background:')) { background = Background( description: line.substring('Background:'.length).trim(), @@ -47,15 +49,11 @@ class BDDFeatureBuilder { i++; if (i < lines.length) { final backgroundLine = lines[i]; - if (backgroundLine.startsWith('Given') || - backgroundLine.startsWith('When') || - backgroundLine.startsWith('Then')) { + if (backgroundLine.startsWith('Given') || backgroundLine.startsWith('When') || backgroundLine.startsWith('Then')) { background.steps.add( Step( backgroundLine.split(' ')[0], - backgroundLine - .substring(backgroundLine.split(' ')[0].length) - .trim(), + backgroundLine.substring(backgroundLine.split(' ')[0].length).trim(), ), ); } @@ -91,9 +89,7 @@ class BDDFeatureBuilder { } // Store decorators for the new scenario - scenarioDecoratorsMap[scenarios.length] = { - ...currentScenarioDecorators - }; + scenarioDecoratorsMap[scenarios.length] = {...currentScenarioDecorators}; currentScenarioDecorators.clear(); // if scenarios.isEmpty, which means the first scenario @@ -107,9 +103,7 @@ class BDDFeatureBuilder { currentScenarioName = line.substring('Scenario:'.length).trim(); currentExamples = null; exampleHeaders = null; - } else if (line.startsWith('Given') || - line.startsWith('When') || - line.startsWith('Then')) { + } else if (line.startsWith('Given') || line.startsWith('When') || line.startsWith('Then')) { final keyword = line.split(' ')[0]; final text = line.substring(keyword.length).trim(); currentSteps.add(Step(keyword, text)); @@ -121,21 +115,11 @@ class BDDFeatureBuilder { if (i < lines.length) { final headerLine = lines[i]; if (headerLine.startsWith('|')) { - exampleHeaders = headerLine - .split('|') - .map((cell) => cell.trim()) - .where((cell) => cell.isNotEmpty) - .toList(); + exampleHeaders = headerLine.split('|').map((cell) => cell.trim()).where((cell) => cell.isNotEmpty).toList(); } } - } else if (line.startsWith('|') && - currentExamples != null && - exampleHeaders != null) { - final cells = line - .split('|') - .map((cell) => cell.trim()) - .where((cell) => cell.isNotEmpty) - .toList(); + } else if (line.startsWith('|') && currentExamples != null && exampleHeaders != null) { + final cells = line.split('|').map((cell) => cell.trim()).where((cell) => cell.isNotEmpty).toList(); if (cells.length == exampleHeaders.length) { currentExamples.add(Map.fromIterables(exampleHeaders, cells)); @@ -186,14 +170,12 @@ class BDDFeatureBuilder { // if the feature has @unitTest and the scenario has @widgetTest, // then remove the @unitTest from the scenario decorators if (featureDecorators.hasUnitTest && decorators.hasWidgetTest) { - return {...featureDecorators, ...decorators} - ..removeWhere((e) => e.isUnitTest); + return {...featureDecorators, ...decorators}..removeWhere((e) => e.isUnitTest); } // if the feature has @widgetTest and the scenario has @unitTest, // then remove the @widgetTest from the scenario decorators if (featureDecorators.hasWidgetTest && decorators.hasUnitTest) { - return {...featureDecorators, ...decorators} - ..removeWhere((e) => e.isWidgetTest); + return {...featureDecorators, ...decorators}..removeWhere((e) => e.isWidgetTest); } return {...featureDecorators, ...decorators}; } diff --git a/lib/src/feature/builder/bdd_builders/bdd_test_file_builder.dart b/lib/src/feature/builder/bdd_builders/bdd_test_file_builder.dart index f3a2302..e7145d5 100644 --- a/lib/src/feature/builder/bdd_builders/bdd_test_file_builder.dart +++ b/lib/src/feature/builder/bdd_builders/bdd_test_file_builder.dart @@ -11,7 +11,7 @@ final spaceStep = ' '; class BDDTestFileBuilder { Future build(BuildStep buildStep, Feature feature) async { final inputId = buildStep.inputId; - final testOutputId = inputId.changeExtension('.bdd_test.dart'); + final testOutputId = inputId.changeExtension('.bdd_test.g.dart'); final testContent = await buildTestFile(feature); await buildStep.writeAsString(testOutputId, testContent); @@ -25,22 +25,20 @@ class BDDTestFileBuilder { buffer.writeln("import 'package:bdd_flutter/bdd_flutter.dart';"); } - buffer.writeln("import '${feature.name.toSnakeCase}.bdd_scenarios.dart';"); + buffer.writeln("import '${feature.name.toSnakeCase}.bdd_scenarios.g.dart';"); buffer.writeln(); buffer.writeln("void main() {"); //add reporter initialization if needed if (feature.decorators.hasEnableReporter) { - buffer.writeln( - " final reporter = BDDTestReporter(featureName: '${feature.name}');"); + buffer.writeln(" final reporter = BDDTestReporter(featureName: '${feature.name}');"); buffer.writeln(" setUpAll(() {"); buffer.writeln(" reporter.testStarted(); // start recording"); buffer.writeln(" });"); buffer.writeln(" tearDownAll(() {"); buffer.writeln(" reporter.testFinished(); // stop recording"); buffer.writeln(" reporter.printReport(); // print report"); - buffer.writeln( - " //reporter.saveReportToFile(); //uncomment to save report to file"); + buffer.writeln(" //reporter.saveReportToFile(); //uncomment to save report to file"); buffer.writeln(" });"); } @@ -63,8 +61,7 @@ class BDDTestFileBuilder { if (isUnitTest) { buffer.writeln(" $testFunction('${scenario.name}', () async {"); } else { - buffer - .writeln(" $testFunction('${scenario.name}', (tester) async {"); + buffer.writeln(" $testFunction('${scenario.name}', (tester) async {"); } buffer.writeln(" //Scenario: ${scenario.name}"); @@ -80,8 +77,7 @@ class BDDTestFileBuilder { for (var example in scenario.examples!) { buffer.write(" {"); for (var entry in example.entries) { - buffer.write( - "'${entry.key.snakeCaseToCamelCase}': '${entry.value}',"); + buffer.write("'${entry.key.snakeCaseToCamelCase}': '${entry.value}',"); } buffer.write("},"); buffer.writeln(); diff --git a/lib/src/feature/builder/bdd_builders/scenario_file_builder.dart b/lib/src/feature/builder/bdd_builders/scenario_file_builder.dart index 43ffb8e..36d67de 100644 --- a/lib/src/feature/builder/bdd_builders/scenario_file_builder.dart +++ b/lib/src/feature/builder/bdd_builders/scenario_file_builder.dart @@ -7,7 +7,7 @@ import '../../../extensions/string_x.dart'; class ScenariosFileBuilder { Future build(BuildStep buildStep, Feature feature) async { final inputId = buildStep.inputId; - final scenarioOutputId = inputId.changeExtension('.bdd_scenarios.dart'); + final scenarioOutputId = inputId.changeExtension('.bdd_scenarios.g.dart'); final scenarioContent = await buildScenarioFile(feature); await buildStep.writeAsString(scenarioOutputId, scenarioContent); diff --git a/lib/src/feature/builder/domain/bdd_ignore.dart b/lib/src/feature/builder/domain/bdd_ignore.dart new file mode 100644 index 0000000..e184312 --- /dev/null +++ b/lib/src/feature/builder/domain/bdd_ignore.dart @@ -0,0 +1,27 @@ +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:yaml/yaml.dart'; + +class BDDIgnore { + static final List _ignoredFiles = []; + static bool _isInitialized = false; + + static Future initialize() async { + if (_isInitialized) return; + + final ignoreFile = File('bdd_ignore.yaml'); + if (await ignoreFile.exists()) { + final content = await ignoreFile.readAsString(); + final yaml = loadYaml(content); + if (yaml is YamlMap && yaml['ignore_files'] is YamlList) { + _ignoredFiles.addAll((yaml['ignore_files'] as YamlList).map((e) => e.toString()).toList()); + } + } + _isInitialized = true; + } + + static bool shouldIgnore(String filePath) { + return _ignoredFiles.any((ignoredPath) => path.normalize(filePath) == path.normalize(ignoredPath)); + } +} diff --git a/lib/src/feature/builder/domain/decorator.dart b/lib/src/feature/builder/domain/decorator.dart index c840f18..321f713 100644 --- a/lib/src/feature/builder/domain/decorator.dart +++ b/lib/src/feature/builder/domain/decorator.dart @@ -4,22 +4,17 @@ class BDDDecorator { BDDDecorator(this.type, this.value); factory BDDDecorator.unitTest() => BDDDecorator(DecoratorType.unitTest, null); - factory BDDDecorator.widgetTest() => - BDDDecorator(DecoratorType.widgetTest, null); - factory BDDDecorator.enableReporter() => - BDDDecorator(DecoratorType.enableReporter, null); - factory BDDDecorator.disableReporter() => - BDDDecorator(DecoratorType.disableReporter, null); - factory BDDDecorator.className(String name) => - BDDDecorator(DecoratorType.className, name); + factory BDDDecorator.widgetTest() => BDDDecorator(DecoratorType.widgetTest, null); + factory BDDDecorator.enableReporter() => BDDDecorator(DecoratorType.enableReporter, null); + factory BDDDecorator.disableReporter() => BDDDecorator(DecoratorType.disableReporter, null); + factory BDDDecorator.className(String name) => BDDDecorator(DecoratorType.className, name); static BDDDecorator fromString(String text) { return switch (text) { '@unitTest' => BDDDecorator(DecoratorType.unitTest, null), '@widgetTest' => BDDDecorator(DecoratorType.widgetTest, null), '@enableReporter' => BDDDecorator(DecoratorType.enableReporter, null), '@disableReporter' => BDDDecorator(DecoratorType.disableReporter, null), - var t when t.contains("@className") => - BDDDecorator(DecoratorType.className, _extractClassNameValue(t)), + var t when t.contains("@className") => BDDDecorator(DecoratorType.className, _extractClassNameValue(t)), _ => BDDDecorator(DecoratorType.unknown, null) }; } diff --git a/lib/src/feature/report/test_reporter.dart b/lib/src/feature/report/test_reporter.dart index 89a65f4..0774ba0 100644 --- a/lib/src/feature/report/test_reporter.dart +++ b/lib/src/feature/report/test_reporter.dart @@ -64,16 +64,9 @@ class BDDTestReporter { _overview = FeatureTestOverview( featureName: featureName, totalScenarios: _scenarios.length, - totalSteps: - _scenarios.fold(0, (sum, scenario) => sum + scenario.steps.length), - totalPassed: _scenarios.fold( - 0, - (sum, scenario) => - sum + scenario.steps.values.where((step) => step.status).length), - totalFailed: _scenarios.fold( - 0, - (sum, scenario) => - sum + scenario.steps.values.where((step) => !step.status).length), + totalSteps: _scenarios.fold(0, (sum, scenario) => sum + scenario.steps.length), + totalPassed: _scenarios.fold(0, (sum, scenario) => sum + scenario.steps.values.where((step) => step.status).length), + totalFailed: _scenarios.fold(0, (sum, scenario) => sum + scenario.steps.values.where((step) => !step.status).length), totalTime: totalTime, ); _generateReport(); @@ -99,15 +92,10 @@ class BDDTestReporter { for (var scenario in _scenarios) { _reportBuffer!.writeln('\tScenario: ${scenario.name}'); for (var step in scenario.steps.values) { - _reportBuffer!.writeln( - '\t\t${step.status ? green : red}${step.name}: ${step.status ? 'โœ“' : 'โœ—'}$reset'); + _reportBuffer!.writeln('\t\t${step.status ? green : red}${step.name}: ${step.status ? 'โœ“' : 'โœ—'}$reset'); } } - final errorSteps = _scenarios - .expand((scenario) => scenario.steps.values - .where((step) => step.error != null) - .map((step) => step)) - .toList(); + final errorSteps = _scenarios.expand((scenario) => scenario.steps.values.where((step) => step.error != null).map((step) => step)).toList(); if (errorSteps.isNotEmpty) { _reportBuffer!.writeln('--------------------------------'); _reportBuffer!.writeln('ERROR:'); @@ -123,8 +111,7 @@ class BDDTestReporter { return; } - final fileName = - '${featureName}_report_${DateTime.now().toIso8601String()}.txt'; + final fileName = '${featureName}_report_${DateTime.now().toIso8601String()}.txt'; final filePath = '$dir/$fileName'; final file = File(filePath); file.writeAsStringSync(_reportBuffer?.toString() ?? ''); diff --git a/pubspec.yaml b/pubspec.yaml index b41d104..a0b5b5e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: bdd_flutter description: "A powerful Flutter package that simplifies Behavior Driven Development (BDD) by automatically generating test files from Gherkin feature files" -version: 0.1.5 +version: 0.2.0 homepage: https://github.com/samderlust/bdd_flutter environment: @@ -10,6 +10,7 @@ environment: dependencies: flutter: sdk: flutter + yaml: ^3.0.0 build: ^2.3.0 @@ -19,3 +20,6 @@ dev_dependencies: flutter_lints: ^5.0.0 build_runner: ^2.3.0 test: ^1.20.0 + +executables: + bdd: bdd diff --git a/test/feature_builder/bdd_flutter_test.dart b/test/feature_builder/bdd_flutter_test.dart index e514d06..08f024b 100644 --- a/test/feature_builder/bdd_flutter_test.dart +++ b/test/feature_builder/bdd_flutter_test.dart @@ -43,8 +43,7 @@ Feature: Comms Permissions expect(feature.name, 'Comms Permissions'); expect(feature.scenarios.length, 1); expect(feature.decorators, contains(BDDDecorator.unitTest())); - expect( - feature.scenarios[0].decorators, contains(BDDDecorator.unitTest())); + expect(feature.scenarios[0].decorators, contains(BDDDecorator.unitTest())); }); test('scenario decorator override feature decorator', () { const featureContent = ''' From 8c573e0fe6529269d32e055d2d59a74caf0114af Mon Sep 17 00:00:00 2001 From: Sang Nguyen Date: Sun, 4 May 2025 10:40:08 -0600 Subject: [PATCH 2/7] add ignore_features --- README.md | 157 +++++++++--------- example/build.yaml | 2 + .../calculator/calculator.bdd_scenarios.dart | 5 + example/test/calculator/calculator.feature | 1 - .../test/counter/counter.bdd_scenarios.g.dart | 3 + example/test/counter/counter.bdd_test.g.dart | 14 +- .../test/sample/sample.bdd_scenarios.g.dart | 5 + example/test/sample/sample.bdd_test.g.dart | 36 ++-- lib/src/builder.dart | 7 + .../feature/builder/domain/bdd_options.dart | 7 +- 10 files changed, 136 insertions(+), 101 deletions(-) diff --git a/README.md b/README.md index 8a725ec..acd73ea 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,22 @@ -# BDD Flutter +# ๐Ÿš€ BDD Flutter + +[![pub package](https://img.shields.io/pub/v/bdd_flutter.svg)](https://pub.dev/packages/bdd_flutter) +[![style: very good analysis](https://img.shields.io/badge/style-very_good_analysis-B22C89.svg)](https://pub.dev/packages/very_good_analysis) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![Buy Me A Coffee](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/samderlust) A powerful Flutter package that simplifies Behavior Driven Development (BDD) by automatically generating test files from Gherkin feature files. Write expressive tests in plain English using Given/When/Then scenarios and let BDD Flutter handle the boilerplate code generation. -## Features +## โœจ Features -- Parse `.feature` files written in Gherkin syntax -- Generate boilerplate test files -- Support for both widget tests and unit tests -- Configurable test generation +- ๐Ÿ“ Parse `.feature` files written in Gherkin syntax +- โšก Generate boilerplate test files automatically +- ๐Ÿงช Support for both widget tests and unit tests +- โš™๏ธ Configurable test generation -## Installation +## ๐Ÿ“ฆ Installation -Add this to your package's `pubspec.yaml` file: +Add the following dependencies to your package's `pubspec.yaml` file: ```yaml dev_dependencies: @@ -19,11 +24,11 @@ dev_dependencies: build_runner: any ``` -## Usage +## ๐Ÿš€ Quick Start -Create a `.feature` file in your project: +1. Create a `.feature` file in your project: -``` +```gherkin Feature: Counter Scenario: Increment Given I have a counter with value 0 @@ -36,26 +41,46 @@ Feature: Counter | 3 | 3 | ``` -Run the build command to generate the test files: +2. Generate test files: ```bash flutter pub run build_runner build ``` -This will generate: - -- a `sample.scenario.dart` file, containing the scenario implementation. -- a `sample_test.dart` file, containing the test implementation. - -then run the test file: +3. Run your tests: ```bash flutter test ``` -## Configuration +## ๐Ÿ’ก Recommendations + +When working with generated test files, follow these best practices: + +1. **Generated Files**: + + - Generated files will have the `.g.dart` extension (e.g., `counter_test.bdd_test.g.dart` or `counter_scenarios.g.dart`) + - After implementing your tests, it's recommended to: + - Remove the `.g` extension from the file name + - Add an ignore decorator to your feature file (@ignore) + - This will prevent the generated files from being overwritten by subsequent builds -You can configure the test generation by passing options to the builder: +2. **Feature File Ignore**: + Add this comment at the top of your feature file: + ```gherkin + @ignore + Feature: Counter + ``` + +This approach ensures that: + +- Your implemented tests won't be overwritten by subsequent builds +- Generated files are properly ignored in version control +- You maintain a clean project structure + +## โš™๏ธ Configuration + +Configure test generation in your `build.yaml`: ```yaml targets: @@ -63,54 +88,38 @@ targets: builders: bdd_flutter|bdd_test_builder: options: - generate_widget_tests: false - enable_reporter: true + generate_widget_tests: false # Default: true + enable_reporter: true # Default: false + ignore_features: + - "test/features/ignored.feature" + - "test/features/another_ignored.feature" ``` -- `generate_widget_tests`: default is true - - - if true, generate widget tests - - if false, generate unit tests - -- `enable_reporter`: default is false - - - if true, enable the reporter for the tests in test file - - if false, disable the reporter for the tests in test file +### Configuration Options -## Decorators +| Option | Type | Default | Description | +| ----------------------- | ---- | ------- | ------------------------------------------------------ | +| `generate_widget_tests` | bool | true | Generate widget tests when true, unit tests when false | +| `enable_reporter` | bool | false | Enable/disable test reporter | +| `ignore_features` | List | [] | List of feature file paths to ignore during generation | -You can use the following decorators to control the test generation: +## ๐Ÿท๏ธ Decorators -- `@unitTest`: - - Generate a unit test (ignore build.yaml config) - - apply to feature, scenario -- `@widgetTest`: - - Generate a widget test (ignore build.yaml config) - - apply to feature, scenario -- `@className`: - - Generate a custom class name for the scenario - - apply to scenario - - example: `@className("CustomName")` -- `@enableReporter`: - - Enable the reporter for the tests in the feature - - apply to feature -- `@disableReporter`: - - Disable the reporter for the tests in the feature - - apply to feature +Control test generation with decorators: -decorators can be used at the feature, scenario level. The lower the level, the more specific the decorator and it will override the upper level decorator. +| Decorator | Scope | Description | +| -------------------------- | ----------------- | --------------------------------------- | +| `@unitTest` | Feature, Scenario | Generate unit test (overrides config) | +| `@widgetTest` | Feature, Scenario | Generate widget test (overrides config) | +| `@className("CustomName")` | Scenario | Generate custom class name | +| `@enableReporter` | Feature | Enable test reporter | +| `@disableReporter` | Feature | Disable test reporter | -## Contributing +> ๐Ÿ’ก Decorators follow a hierarchy: Scenario-level decorators override Feature-level ones. -Contributions are welcome! Please open an issue or submit a pull request. +## ๐Ÿ“ Complete Example -## License - -This project is licensed under the MIT License - see the LICENSE file for details. - -## Example - -1. create a feature file (e.g. `counter.feature`) +### 1. Feature File (`counter.feature`) ```gherkin Feature: Counter @@ -125,15 +134,9 @@ Feature: Counter | 3 | 3 | ``` -2. run the builder - -```bash -flutter pub run build_runner build -``` - -3. generated file +### 2. Generated Files -- `counter_scenarios.dart` +#### `counter_scenarios.dart` ```dart import 'package:flutter_test/flutter_test.dart'; @@ -143,17 +146,17 @@ class IncrementScenario { // TODO: Implement Given I have a counter with value 0 } - static Future iIncrementTheCounterBy(WidgetTester tester,dynamic value) async { + static Future iIncrementTheCounterBy(WidgetTester tester, dynamic value) async { // TODO: Implement When I increment the counter by } - static Future theCounterShouldHaveValue(WidgetTester tester,dynamic expected_value) async { + static Future theCounterShouldHaveValue(WidgetTester tester, dynamic expected_value) async { // TODO: Implement Then the counter should have value } } ``` -- `counter_test.dart` +#### `counter_test.dart` ```dart import 'package:flutter_test/flutter_test.dart'; @@ -177,8 +180,14 @@ void main() { } ``` -4. run the test +## ๐Ÿค Contributing -```bash -flutter test -``` +We welcome contributions! Please feel free to: + +- Open an issue +- Submit a pull request +- Share your feedback + +## ๐Ÿ“„ License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. diff --git a/example/build.yaml b/example/build.yaml index fb450e9..5c39d90 100644 --- a/example/build.yaml +++ b/example/build.yaml @@ -5,3 +5,5 @@ targets: options: # generate_widget_tests: # enable_reporter: true + ignore_features: + - "test/calculator/calculator.feature" diff --git a/example/test/calculator/calculator.bdd_scenarios.dart b/example/test/calculator/calculator.bdd_scenarios.dart index 12fad21..66e5be3 100644 --- a/example/test/calculator/calculator.bdd_scenarios.dart +++ b/example/test/calculator/calculator.bdd_scenarios.dart @@ -12,6 +12,7 @@ class AddTwoNumbersScenario { static Future theResultShouldBe3(WidgetTester tester) async { // TODO: Implement Then the result should be 3 } + } class SubtractTwoNumbersScenario { @@ -26,6 +27,7 @@ class SubtractTwoNumbersScenario { static Future theResultShouldBe2(WidgetTester tester) async { // TODO: Implement Then the result should be 2 } + } class MultiplyTwoNumbersScenario { @@ -40,6 +42,7 @@ class MultiplyTwoNumbersScenario { static Future theResultShouldBe6(WidgetTester tester) async { // TODO: Implement Then the result should be 6 } + } class DivideTwoNumbersScenario { @@ -54,4 +57,6 @@ class DivideTwoNumbersScenario { static Future theResultShouldBe(WidgetTester tester, String result) async { // TODO: Implement Then the result should be } + } + diff --git a/example/test/calculator/calculator.feature b/example/test/calculator/calculator.feature index ab8a6d0..83d3341 100644 --- a/example/test/calculator/calculator.feature +++ b/example/test/calculator/calculator.feature @@ -1,4 +1,3 @@ -@ignore Feature: Calculator Scenario: Add two numbers diff --git a/example/test/counter/counter.bdd_scenarios.g.dart b/example/test/counter/counter.bdd_scenarios.g.dart index cd8095f..eaeabd3 100644 --- a/example/test/counter/counter.bdd_scenarios.g.dart +++ b/example/test/counter/counter.bdd_scenarios.g.dart @@ -4,6 +4,7 @@ class CounterBackground { static Future iHaveACounterWithValue0() async { // TODO: Implement Given I have a counter with value 0 } + } class IncrementScenario { @@ -14,4 +15,6 @@ class IncrementScenario { static Future theCounterShouldHaveValue(WidgetTester tester, String expectedvalue) async { // TODO: Implement Then the counter should have value } + } + diff --git a/example/test/counter/counter.bdd_test.g.dart b/example/test/counter/counter.bdd_test.g.dart index 54c263c..8b1940c 100644 --- a/example/test/counter/counter.bdd_test.g.dart +++ b/example/test/counter/counter.bdd_test.g.dart @@ -8,15 +8,15 @@ void main() { testWidgets('Increment', (tester) async { //Scenario: Increment final examples = [ - {'value': '1', 'expectedvalue': '1'}, - {'value': '2', 'expectedvalue': '2'}, - {'value': '3', 'expectedvalue': '3'}, + {'value': '1','expectedvalue': '1',}, + {'value': '2','expectedvalue': '2',}, + {'value': '3','expectedvalue': '3',}, ]; for (var example in examples) { - // When I increment the counter by - await IncrementScenario.iIncrementTheCounterBy(tester, example['value']!); - // Then the counter should have value - await IncrementScenario.theCounterShouldHaveValue(tester, example['expected_value']!); + // When I increment the counter by + await IncrementScenario.iIncrementTheCounterBy(tester, example['value']!); + // Then the counter should have value + await IncrementScenario.theCounterShouldHaveValue(tester, example['expected_value']!); } }); }); diff --git a/example/test/sample/sample.bdd_scenarios.g.dart b/example/test/sample/sample.bdd_scenarios.g.dart index b75acf7..0b8db30 100644 --- a/example/test/sample/sample.bdd_scenarios.g.dart +++ b/example/test/sample/sample.bdd_scenarios.g.dart @@ -12,6 +12,7 @@ class SampleScenario { static Future iShouldSeeTheSampleFeature(WidgetTester tester) async { // TODO: Implement Then I should see the sample feature } + } class CounterScenario { @@ -26,6 +27,7 @@ class CounterScenario { static Future iShouldSeeTheCounterIncremented(WidgetTester tester) async { // TODO: Implement Then I should see the counter incremented } + } class CounterWithExamplesScenario { @@ -40,6 +42,7 @@ class CounterWithExamplesScenario { static Future iShouldSeeTheCounterIncremented(WidgetTester tester) async { // TODO: Implement Then I should see the counter incremented } + } class CounterWithParametersScenario { @@ -54,4 +57,6 @@ class CounterWithParametersScenario { static Future iShouldSeeTheResult(WidgetTester tester, String result) async { // TODO: Implement Then I should see the result } + } + diff --git a/example/test/sample/sample.bdd_test.g.dart b/example/test/sample/sample.bdd_test.g.dart index e47642e..2e9d19f 100644 --- a/example/test/sample/sample.bdd_test.g.dart +++ b/example/test/sample/sample.bdd_test.g.dart @@ -24,33 +24,33 @@ void main() { testWidgets('Counter with examples', (tester) async { //Scenario: Counter with examples final examples = [ - {'counter': '1'}, - {'counter': '2'}, - {'counter': '3'}, + {'counter': '1',}, + {'counter': '2',}, + {'counter': '3',}, ]; for (var example in examples) { - // Given I have a counter - await CounterWithExamplesScenario.iHaveACounter(tester); - // When I increment the - await CounterWithExamplesScenario.iIncrementThe(tester, example['counter']!); - // Then I should see the counter incremented - await CounterWithExamplesScenario.iShouldSeeTheCounterIncremented(tester); + // Given I have a counter + await CounterWithExamplesScenario.iHaveACounter(tester); + // When I increment the + await CounterWithExamplesScenario.iIncrementThe(tester, example['counter']!); + // Then I should see the counter incremented + await CounterWithExamplesScenario.iShouldSeeTheCounterIncremented(tester); } }); testWidgets('Counter with parameters', (tester) async { //Scenario: Counter with parameters final examples = [ - {'counter': '1', 'result': '2'}, - {'counter': '2', 'result': '3'}, - {'counter': '3', 'result': '4'}, + {'counter': '1','result': '2',}, + {'counter': '2','result': '3',}, + {'counter': '3','result': '4',}, ]; for (var example in examples) { - // Given I have a counter - await CounterWithParametersScenario.iHaveACounter(tester); - // When I increment the counter - await CounterWithParametersScenario.iIncrementTheCounter(tester, example['counter']!); - // Then I should see the result - await CounterWithParametersScenario.iShouldSeeTheResult(tester, example['result']!); + // Given I have a counter + await CounterWithParametersScenario.iHaveACounter(tester); + // When I increment the counter + await CounterWithParametersScenario.iIncrementTheCounter(tester, example['counter']!); + // Then I should see the result + await CounterWithParametersScenario.iShouldSeeTheResult(tester, example['result']!); } }); }); diff --git a/lib/src/builder.dart b/lib/src/builder.dart index 5871afe..9b4495e 100644 --- a/lib/src/builder.dart +++ b/lib/src/builder.dart @@ -22,6 +22,11 @@ class BDDTestBuilder implements Builder { Future build(BuildStep buildStep) async { await BDDIgnore.initialize(); + // Check if the feature file should be ignored + if (options.ignoreFeatures.contains(buildStep.inputId.path)) { + return; + } + final factory = BDDFactory.create(options); final feature = await factory.featureBuilder.build(buildStep); @@ -46,10 +51,12 @@ Builder bddTestBuilder(BuilderOptions options) { final config = options.config; final generateWidgetTests = config['generate_widget_tests'] as bool? ?? true; final enableReporter = config['enable_reporter'] as bool? ?? false; + final ignoreFeatures = (config['ignore_features'] as List?)?.cast() ?? []; final bddOptions = BDDOptions( generateWidgetTests: generateWidgetTests, enableReporter: enableReporter, + ignoreFeatures: ignoreFeatures, ); return BDDTestBuilder(options: bddOptions); } diff --git a/lib/src/feature/builder/domain/bdd_options.dart b/lib/src/feature/builder/domain/bdd_options.dart index ff74802..651da36 100644 --- a/lib/src/feature/builder/domain/bdd_options.dart +++ b/lib/src/feature/builder/domain/bdd_options.dart @@ -1,6 +1,11 @@ class BDDOptions { final bool generateWidgetTests; final bool enableReporter; + final List ignoreFeatures; - BDDOptions({this.generateWidgetTests = true, this.enableReporter = false}); + BDDOptions({ + this.generateWidgetTests = true, + this.enableReporter = false, + this.ignoreFeatures = const [], + }); } From f202f171e04d15eba8ea3cafedac90beb00f09b3 Mon Sep 17 00:00:00 2001 From: Sang Nguyen Date: Sun, 4 May 2025 10:42:05 -0600 Subject: [PATCH 3/7] add actions --- .github/workflows/test_and_format.yml | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/test_and_format.yml diff --git a/.github/workflows/test_and_format.yml b/.github/workflows/test_and_format.yml new file mode 100644 index 0000000..9ea9aa3 --- /dev/null +++ b/.github/workflows/test_and_format.yml @@ -0,0 +1,30 @@ +name: Flutter Test and Format + +on: + pull_request: + branches: [main] + +jobs: + test_and_format: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: "3.x" + channel: "stable" + + - name: Install dependencies + run: flutter pub get + + - name: Run tests + run: flutter test + + - name: Format code + run: flutter format . --line-length 200 + + - name: Check formatting + run: flutter format --set-exit-if-changed . --line-length 200 --exclude "example/pubspec.lock" From 12ac025f6ffcd296ba23d7633f7275ee9cc226ec Mon Sep 17 00:00:00 2001 From: Sang Nguyen Date: Sun, 4 May 2025 10:43:32 -0600 Subject: [PATCH 4/7] update change log ' --- CHANGELOG.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d33445..d88db84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,10 @@ ## 0.2.0 -- remove `@ignore` decorator -- change output files extension to `.bdd_scenarios.dart` and `.bdd_test.dart` +- change output files extension to `.bdd_scenarios.g.dart` and `.bdd_test.g.dart` +- add `ignore_features` option to `build.yaml` +- fix example +- fix import file name +- update readme ## 0.1.5 From ae3c567ffc6c813a80bbec49aa6063e44ee181a6 Mon Sep 17 00:00:00 2001 From: Sang Nguyen Date: Sun, 4 May 2025 10:47:07 -0600 Subject: [PATCH 5/7] fix action --- .github/workflows/test_and_format.yml | 8 +++-- .../calculator/calculator.bdd_scenarios.dart | 5 --- .../test/counter/counter.bdd_scenarios.g.dart | 3 -- example/test/counter/counter.bdd_test.g.dart | 14 ++++---- .../test/sample/sample.bdd_scenarios.g.dart | 5 --- example/test/sample/sample.bdd_test.g.dart | 36 +++++++++---------- 6 files changed, 30 insertions(+), 41 deletions(-) diff --git a/.github/workflows/test_and_format.yml b/.github/workflows/test_and_format.yml index 9ea9aa3..dfbd3c7 100644 --- a/.github/workflows/test_and_format.yml +++ b/.github/workflows/test_and_format.yml @@ -24,7 +24,9 @@ jobs: run: flutter test - name: Format code - run: flutter format . --line-length 200 + run: | + dart format . --line-length 200 --output=none $(find . -name "*.dart" ! -name "*.g.dart") - - name: Check formatting - run: flutter format --set-exit-if-changed . --line-length 200 --exclude "example/pubspec.lock" + - name: Check for changes + run: | + git diff --exit-code -- . ':!example/pubspec.lock' || (echo "::error::Code formatting issues found. Run 'dart format --line-length 200 lib/ test/ example/' to fix." && exit 1) diff --git a/example/test/calculator/calculator.bdd_scenarios.dart b/example/test/calculator/calculator.bdd_scenarios.dart index 66e5be3..12fad21 100644 --- a/example/test/calculator/calculator.bdd_scenarios.dart +++ b/example/test/calculator/calculator.bdd_scenarios.dart @@ -12,7 +12,6 @@ class AddTwoNumbersScenario { static Future theResultShouldBe3(WidgetTester tester) async { // TODO: Implement Then the result should be 3 } - } class SubtractTwoNumbersScenario { @@ -27,7 +26,6 @@ class SubtractTwoNumbersScenario { static Future theResultShouldBe2(WidgetTester tester) async { // TODO: Implement Then the result should be 2 } - } class MultiplyTwoNumbersScenario { @@ -42,7 +40,6 @@ class MultiplyTwoNumbersScenario { static Future theResultShouldBe6(WidgetTester tester) async { // TODO: Implement Then the result should be 6 } - } class DivideTwoNumbersScenario { @@ -57,6 +54,4 @@ class DivideTwoNumbersScenario { static Future theResultShouldBe(WidgetTester tester, String result) async { // TODO: Implement Then the result should be } - } - diff --git a/example/test/counter/counter.bdd_scenarios.g.dart b/example/test/counter/counter.bdd_scenarios.g.dart index eaeabd3..cd8095f 100644 --- a/example/test/counter/counter.bdd_scenarios.g.dart +++ b/example/test/counter/counter.bdd_scenarios.g.dart @@ -4,7 +4,6 @@ class CounterBackground { static Future iHaveACounterWithValue0() async { // TODO: Implement Given I have a counter with value 0 } - } class IncrementScenario { @@ -15,6 +14,4 @@ class IncrementScenario { static Future theCounterShouldHaveValue(WidgetTester tester, String expectedvalue) async { // TODO: Implement Then the counter should have value } - } - diff --git a/example/test/counter/counter.bdd_test.g.dart b/example/test/counter/counter.bdd_test.g.dart index 8b1940c..54c263c 100644 --- a/example/test/counter/counter.bdd_test.g.dart +++ b/example/test/counter/counter.bdd_test.g.dart @@ -8,15 +8,15 @@ void main() { testWidgets('Increment', (tester) async { //Scenario: Increment final examples = [ - {'value': '1','expectedvalue': '1',}, - {'value': '2','expectedvalue': '2',}, - {'value': '3','expectedvalue': '3',}, + {'value': '1', 'expectedvalue': '1'}, + {'value': '2', 'expectedvalue': '2'}, + {'value': '3', 'expectedvalue': '3'}, ]; for (var example in examples) { - // When I increment the counter by - await IncrementScenario.iIncrementTheCounterBy(tester, example['value']!); - // Then the counter should have value - await IncrementScenario.theCounterShouldHaveValue(tester, example['expected_value']!); + // When I increment the counter by + await IncrementScenario.iIncrementTheCounterBy(tester, example['value']!); + // Then the counter should have value + await IncrementScenario.theCounterShouldHaveValue(tester, example['expected_value']!); } }); }); diff --git a/example/test/sample/sample.bdd_scenarios.g.dart b/example/test/sample/sample.bdd_scenarios.g.dart index 0b8db30..b75acf7 100644 --- a/example/test/sample/sample.bdd_scenarios.g.dart +++ b/example/test/sample/sample.bdd_scenarios.g.dart @@ -12,7 +12,6 @@ class SampleScenario { static Future iShouldSeeTheSampleFeature(WidgetTester tester) async { // TODO: Implement Then I should see the sample feature } - } class CounterScenario { @@ -27,7 +26,6 @@ class CounterScenario { static Future iShouldSeeTheCounterIncremented(WidgetTester tester) async { // TODO: Implement Then I should see the counter incremented } - } class CounterWithExamplesScenario { @@ -42,7 +40,6 @@ class CounterWithExamplesScenario { static Future iShouldSeeTheCounterIncremented(WidgetTester tester) async { // TODO: Implement Then I should see the counter incremented } - } class CounterWithParametersScenario { @@ -57,6 +54,4 @@ class CounterWithParametersScenario { static Future iShouldSeeTheResult(WidgetTester tester, String result) async { // TODO: Implement Then I should see the result } - } - diff --git a/example/test/sample/sample.bdd_test.g.dart b/example/test/sample/sample.bdd_test.g.dart index 2e9d19f..e47642e 100644 --- a/example/test/sample/sample.bdd_test.g.dart +++ b/example/test/sample/sample.bdd_test.g.dart @@ -24,33 +24,33 @@ void main() { testWidgets('Counter with examples', (tester) async { //Scenario: Counter with examples final examples = [ - {'counter': '1',}, - {'counter': '2',}, - {'counter': '3',}, + {'counter': '1'}, + {'counter': '2'}, + {'counter': '3'}, ]; for (var example in examples) { - // Given I have a counter - await CounterWithExamplesScenario.iHaveACounter(tester); - // When I increment the - await CounterWithExamplesScenario.iIncrementThe(tester, example['counter']!); - // Then I should see the counter incremented - await CounterWithExamplesScenario.iShouldSeeTheCounterIncremented(tester); + // Given I have a counter + await CounterWithExamplesScenario.iHaveACounter(tester); + // When I increment the + await CounterWithExamplesScenario.iIncrementThe(tester, example['counter']!); + // Then I should see the counter incremented + await CounterWithExamplesScenario.iShouldSeeTheCounterIncremented(tester); } }); testWidgets('Counter with parameters', (tester) async { //Scenario: Counter with parameters final examples = [ - {'counter': '1','result': '2',}, - {'counter': '2','result': '3',}, - {'counter': '3','result': '4',}, + {'counter': '1', 'result': '2'}, + {'counter': '2', 'result': '3'}, + {'counter': '3', 'result': '4'}, ]; for (var example in examples) { - // Given I have a counter - await CounterWithParametersScenario.iHaveACounter(tester); - // When I increment the counter - await CounterWithParametersScenario.iIncrementTheCounter(tester, example['counter']!); - // Then I should see the result - await CounterWithParametersScenario.iShouldSeeTheResult(tester, example['result']!); + // Given I have a counter + await CounterWithParametersScenario.iHaveACounter(tester); + // When I increment the counter + await CounterWithParametersScenario.iIncrementTheCounter(tester, example['counter']!); + // Then I should see the result + await CounterWithParametersScenario.iShouldSeeTheResult(tester, example['result']!); } }); }); From 047a60a4318d9d3842f8d9638735797568b05c2d Mon Sep 17 00:00:00 2001 From: Sang Nguyen Date: Sun, 4 May 2025 11:01:02 -0600 Subject: [PATCH 6/7] fix parsing --- .../test/counter/counter.bdd_scenarios.g.dart | 3 ++ example/test/counter/counter.bdd_test.g.dart | 14 +++--- .../test/sample/sample.bdd_scenarios.g.dart | 19 +++++--- example/test/sample/sample.bdd_test.g.dart | 46 +++++++++---------- .../bdd_builders/bdd_feature_builder.dart | 10 +++- test/feature_builder/bdd_flutter_test.dart | 25 +++++----- 6 files changed, 67 insertions(+), 50 deletions(-) diff --git a/example/test/counter/counter.bdd_scenarios.g.dart b/example/test/counter/counter.bdd_scenarios.g.dart index cd8095f..eaeabd3 100644 --- a/example/test/counter/counter.bdd_scenarios.g.dart +++ b/example/test/counter/counter.bdd_scenarios.g.dart @@ -4,6 +4,7 @@ class CounterBackground { static Future iHaveACounterWithValue0() async { // TODO: Implement Given I have a counter with value 0 } + } class IncrementScenario { @@ -14,4 +15,6 @@ class IncrementScenario { static Future theCounterShouldHaveValue(WidgetTester tester, String expectedvalue) async { // TODO: Implement Then the counter should have value } + } + diff --git a/example/test/counter/counter.bdd_test.g.dart b/example/test/counter/counter.bdd_test.g.dart index 54c263c..8b1940c 100644 --- a/example/test/counter/counter.bdd_test.g.dart +++ b/example/test/counter/counter.bdd_test.g.dart @@ -8,15 +8,15 @@ void main() { testWidgets('Increment', (tester) async { //Scenario: Increment final examples = [ - {'value': '1', 'expectedvalue': '1'}, - {'value': '2', 'expectedvalue': '2'}, - {'value': '3', 'expectedvalue': '3'}, + {'value': '1','expectedvalue': '1',}, + {'value': '2','expectedvalue': '2',}, + {'value': '3','expectedvalue': '3',}, ]; for (var example in examples) { - // When I increment the counter by - await IncrementScenario.iIncrementTheCounterBy(tester, example['value']!); - // Then the counter should have value - await IncrementScenario.theCounterShouldHaveValue(tester, example['expected_value']!); + // When I increment the counter by + await IncrementScenario.iIncrementTheCounterBy(tester, example['value']!); + // Then the counter should have value + await IncrementScenario.theCounterShouldHaveValue(tester, example['expected_value']!); } }); }); diff --git a/example/test/sample/sample.bdd_scenarios.g.dart b/example/test/sample/sample.bdd_scenarios.g.dart index b75acf7..2ea77d9 100644 --- a/example/test/sample/sample.bdd_scenarios.g.dart +++ b/example/test/sample/sample.bdd_scenarios.g.dart @@ -12,9 +12,10 @@ class SampleScenario { static Future iShouldSeeTheSampleFeature(WidgetTester tester) async { // TODO: Implement Then I should see the sample feature } + } -class CounterScenario { +class CounterCustomName { static Future iHaveACounter(WidgetTester tester) async { // TODO: Implement Given I have a counter } @@ -26,32 +27,36 @@ class CounterScenario { static Future iShouldSeeTheCounterIncremented(WidgetTester tester) async { // TODO: Implement Then I should see the counter incremented } + } class CounterWithExamplesScenario { - static Future iHaveACounter(WidgetTester tester) async { + static Future iHaveACounter() async { // TODO: Implement Given I have a counter } - static Future iIncrementThe(WidgetTester tester, String counter) async { + static Future iIncrementThe(String counter) async { // TODO: Implement When I increment the } - static Future iShouldSeeTheCounterIncremented(WidgetTester tester) async { + static Future iShouldSeeTheCounterIncremented() async { // TODO: Implement Then I should see the counter incremented } + } class CounterWithParametersScenario { - static Future iHaveACounter(WidgetTester tester) async { + static Future iHaveACounter() async { // TODO: Implement Given I have a counter } - static Future iIncrementTheCounter(WidgetTester tester, String counter) async { + static Future iIncrementTheCounter(String counter) async { // TODO: Implement When I increment the counter } - static Future iShouldSeeTheResult(WidgetTester tester, String result) async { + static Future iShouldSeeTheResult(String result) async { // TODO: Implement Then I should see the result } + } + diff --git a/example/test/sample/sample.bdd_test.g.dart b/example/test/sample/sample.bdd_test.g.dart index e47642e..2e514fa 100644 --- a/example/test/sample/sample.bdd_test.g.dart +++ b/example/test/sample/sample.bdd_test.g.dart @@ -15,42 +15,42 @@ void main() { testWidgets('Counter', (tester) async { //Scenario: Counter // Given I have a counter - await CounterScenario.iHaveACounter(tester); + await CounterCustomName.iHaveACounter(tester); // When I increment the counter - await CounterScenario.iIncrementTheCounter(tester); + await CounterCustomName.iIncrementTheCounter(tester); // Then I should see the counter incremented - await CounterScenario.iShouldSeeTheCounterIncremented(tester); + await CounterCustomName.iShouldSeeTheCounterIncremented(tester); }); - testWidgets('Counter with examples', (tester) async { + test('Counter with examples', () async { //Scenario: Counter with examples final examples = [ - {'counter': '1'}, - {'counter': '2'}, - {'counter': '3'}, + {'counter': '1',}, + {'counter': '2',}, + {'counter': '3',}, ]; for (var example in examples) { - // Given I have a counter - await CounterWithExamplesScenario.iHaveACounter(tester); - // When I increment the - await CounterWithExamplesScenario.iIncrementThe(tester, example['counter']!); - // Then I should see the counter incremented - await CounterWithExamplesScenario.iShouldSeeTheCounterIncremented(tester); + // Given I have a counter + await CounterWithExamplesScenario.iHaveACounter(); + // When I increment the + await CounterWithExamplesScenario.iIncrementThe( example['counter']!); + // Then I should see the counter incremented + await CounterWithExamplesScenario.iShouldSeeTheCounterIncremented(); } }); - testWidgets('Counter with parameters', (tester) async { + test('Counter with parameters', () async { //Scenario: Counter with parameters final examples = [ - {'counter': '1', 'result': '2'}, - {'counter': '2', 'result': '3'}, - {'counter': '3', 'result': '4'}, + {'counter': '1','result': '2',}, + {'counter': '2','result': '3',}, + {'counter': '3','result': '4',}, ]; for (var example in examples) { - // Given I have a counter - await CounterWithParametersScenario.iHaveACounter(tester); - // When I increment the counter - await CounterWithParametersScenario.iIncrementTheCounter(tester, example['counter']!); - // Then I should see the result - await CounterWithParametersScenario.iShouldSeeTheResult(tester, example['result']!); + // Given I have a counter + await CounterWithParametersScenario.iHaveACounter(); + // When I increment the counter + await CounterWithParametersScenario.iIncrementTheCounter( example['counter']!); + // Then I should see the result + await CounterWithParametersScenario.iShouldSeeTheResult( example['result']!); } }); }); diff --git a/lib/src/feature/builder/bdd_builders/bdd_feature_builder.dart b/lib/src/feature/builder/bdd_builders/bdd_feature_builder.dart index d550288..d9387a3 100644 --- a/lib/src/feature/builder/bdd_builders/bdd_feature_builder.dart +++ b/lib/src/feature/builder/bdd_builders/bdd_feature_builder.dart @@ -58,7 +58,6 @@ class BDDFeatureBuilder { ); } } - } else if (line.startsWith('@')) { } else if (line.startsWith('@')) { if (featureName == null) { featureDecorators.add(BDDDecorator.fromString(line)); @@ -167,6 +166,15 @@ class BDDFeatureBuilder { Set featureDecorators, ) { DecoratorsValidator.validateScenarioDecorators(decorators); + + // If no decorators are specified, use the default from options + if (!decorators.hasUnitTest && !decorators.hasWidgetTest) { + if (!featureDecorators.hasUnitTest && !featureDecorators.hasWidgetTest) { + return options.generateWidgetTests ? {...decorators, BDDDecorator.widgetTest()} : {...decorators, BDDDecorator.unitTest()}; + } + return {...decorators, ...featureDecorators}; + } + // if the feature has @unitTest and the scenario has @widgetTest, // then remove the @unitTest from the scenario decorators if (featureDecorators.hasUnitTest && decorators.hasWidgetTest) { diff --git a/test/feature_builder/bdd_flutter_test.dart b/test/feature_builder/bdd_flutter_test.dart index 08f024b..8af323c 100644 --- a/test/feature_builder/bdd_flutter_test.dart +++ b/test/feature_builder/bdd_flutter_test.dart @@ -1,6 +1,7 @@ import 'package:bdd_flutter/src/feature/builder/bdd_builders/bdd_feature_builder.dart'; import 'package:bdd_flutter/src/feature/builder/domain/bdd_options.dart'; import 'package:bdd_flutter/src/feature/builder/domain/decorator.dart'; +import 'package:bdd_flutter/src/feature/builder/domain/scenario.dart'; import 'package:test/test.dart'; void main() { @@ -20,14 +21,14 @@ Feature: Comms Permissions | true | shows the create new DM chat button | | false | hides the create new DM chat button | '''; - final feature = featureBuilder.parseFeature(featureContent)!; - expect(feature.name, 'Comms Permissions'); - expect(feature.scenarios.length, 1); + final feature = featureBuilder.parseFeature(featureContent); + expect(feature.name, equals('Comms Permissions')); + expect(feature.scenarios.length, equals(1)); final scenario = feature.scenarios.first; - expect(scenario.decorators, contains(BDDDecorator.widgetTest())); + expect(scenario.isWidgetTest, isTrue); - expect(scenario.steps.length, 3); - expect(scenario.examples?.length, 2); + expect(scenario.steps.length, equals(3)); + expect(scenario.examples?.length, equals(2)); }); test('feature decorator override default', () { @@ -39,9 +40,9 @@ Feature: Comms Permissions When user attempts to create a direct channel Then '''; - final feature = featureBuilder.parseFeature(featureContent)!; - expect(feature.name, 'Comms Permissions'); - expect(feature.scenarios.length, 1); + final feature = featureBuilder.parseFeature(featureContent); + expect(feature.name, equals('Comms Permissions')); + expect(feature.scenarios.length, equals(1)); expect(feature.decorators, contains(BDDDecorator.unitTest())); expect(feature.scenarios[0].decorators, contains(BDDDecorator.unitTest())); }); @@ -55,9 +56,9 @@ Feature: Comms Permissions When user attempts to create a direct channel Then '''; - final feature = featureBuilder.parseFeature(featureContent)!; - expect(feature.name, 'Comms Permissions'); - expect(feature.scenarios.length, 1); + final feature = featureBuilder.parseFeature(featureContent); + expect(feature.name, equals('Comms Permissions')); + expect(feature.scenarios.length, equals(1)); expect(feature.decorators, contains(BDDDecorator.unitTest())); expect( feature.scenarios.first.decorators, From 479e0ac4b04da2aa333c5d5c2891cc96f9a4fc88 Mon Sep 17 00:00:00 2001 From: Sang Nguyen Date: Sun, 4 May 2025 11:03:08 -0600 Subject: [PATCH 7/7] fix action --- .github/workflows/test_and_format.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_and_format.yml b/.github/workflows/test_and_format.yml index dfbd3c7..2ab9b91 100644 --- a/.github/workflows/test_and_format.yml +++ b/.github/workflows/test_and_format.yml @@ -21,7 +21,7 @@ jobs: run: flutter pub get - name: Run tests - run: flutter test + run: flutter test ./test - name: Format code run: |