diff --git a/.gitignore b/.gitignore index 9681a66..05758c1 100644 --- a/.gitignore +++ b/.gitignore @@ -122,3 +122,8 @@ app.*.map.json .vscode/ .env + +# Patrol-related +test_bundle.dart +playwright-report/ +test-results/ diff --git a/example/pubspec.lock b/example/pubspec.lock index 6d2c58e..3e6146b 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -1,6 +1,30 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d + url: "https://pub.dev" + source: hosted + version: "91.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08 + url: "https://pub.dev" + source: hosted + version: "8.4.1" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" assorted_layout_widgets: dependency: "direct main" description: @@ -23,7 +47,7 @@ packages: path: ".." relative: true source: path - version: "4.0.6" + version: "4.0.7" boolean_selector: dependency: transitive description: @@ -40,6 +64,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" clock: dependency: transitive description: @@ -56,6 +88,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" + url: "https://pub.dev" + source: hosted + version: "1.15.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + dispose_scope: + dependency: transitive + description: + name: dispose_scope + sha256: "48ec38ca2631c53c4f8fa96b294c801e55c335db5e3fb9f82cede150cfe5a2af" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + equatable: + dependency: transitive + description: + name: equatable + sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b" + url: "https://pub.dev" + source: hosted + version: "2.0.8" fake_async: dependency: transitive description: @@ -64,6 +136,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" flutter: dependency: "direct main" description: flutter @@ -82,6 +162,70 @@ packages: description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 + url: "https://pub.dev" + source: hosted + version: "4.11.0" leak_tracker: dependency: transitive description: @@ -114,6 +258,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: @@ -142,10 +294,34 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "2.2.0" path: dependency: transitive description: @@ -154,11 +330,99 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + patrol: + dependency: transitive + description: + name: patrol + sha256: "0688a00e0fda2e42f102863bbac969b7b5ea836d4dc365b750e0e5aed59d34b0" + url: "https://pub.dev" + source: hosted + version: "4.1.1" + patrol_finders: + dependency: transitive + description: + name: patrol_finders + sha256: ac0bfaf3eaaa6cc3d49c8a365329cc7f4361a5f486f1adb45edc96dbfc854da9 + url: "https://pub.dev" + source: hosted + version: "3.1.0" + patrol_log: + dependency: transitive + description: + name: patrol_log + sha256: "26af8e1a8bbea313c82664d4eff1cace4fc8acbc2893799dd67fe4c5d3fa47df" + url: "https://pub.dev" + source: hosted + version: "0.7.0" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.0" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + url: "https://pub.dev" + source: hosted + version: "0.10.13" source_span: dependency: transitive description: @@ -199,14 +463,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.2" + test: + dependency: transitive + description: + name: test + sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" + url: "https://pub.dev" + source: hosted + version: "1.26.3" test_api: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + url: "https://pub.dev" + source: hosted + version: "0.7.7" + test_core: + dependency: transitive + description: + name: test_core + sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.6.12" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" vector_math: dependency: transitive description: @@ -223,6 +511,54 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.2" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" sdks: - dart: ">=3.8.0-0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.32.0" diff --git a/lib/bdd_framework.dart b/lib/bdd_framework.dart index aff3a85..42a7ae1 100644 --- a/lib/bdd_framework.dart +++ b/lib/bdd_framework.dart @@ -1,5 +1 @@ -export 'src/bdd_base.dart'; -export 'src/bdd_context.dart'; -export 'src/reporters/console_reporter.dart'; -export 'src/reporters/feature_file_reporter.dart'; -export 'src/reporters/html_reporter.dart'; +export 'flutter_test.dart'; diff --git a/lib/dart_test.dart b/lib/dart_test.dart new file mode 100644 index 0000000..aeb9777 --- /dev/null +++ b/lib/dart_test.dart @@ -0,0 +1,3 @@ +export 'src/core.dart'; +export 'src/reporter.dart'; +export 'src/runners/dart_test_runner.dart'; diff --git a/lib/flutter_test.dart b/lib/flutter_test.dart new file mode 100644 index 0000000..f4ed30d --- /dev/null +++ b/lib/flutter_test.dart @@ -0,0 +1,3 @@ +export 'src/core.dart'; +export 'src/reporter.dart'; +export 'src/runners/flutter_test_runner.dart'; diff --git a/lib/flutter_widget_test.dart b/lib/flutter_widget_test.dart new file mode 100644 index 0000000..2ea0247 --- /dev/null +++ b/lib/flutter_widget_test.dart @@ -0,0 +1,3 @@ +export 'src/core.dart'; +export 'src/reporter.dart'; +export 'src/runners/flutter_widget_test_runner.dart'; diff --git a/lib/patrol_test.dart b/lib/patrol_test.dart new file mode 100644 index 0000000..16a0da1 --- /dev/null +++ b/lib/patrol_test.dart @@ -0,0 +1,3 @@ +export 'src/core.dart'; +export 'src/reporter.dart'; +export 'src/runners/patrol_test_runner.dart'; diff --git a/lib/src/core.dart b/lib/src/core.dart new file mode 100644 index 0000000..bb07998 --- /dev/null +++ b/lib/src/core.dart @@ -0,0 +1,3 @@ +export 'core/bdd_base.dart'; +export 'core/bdd_context.dart'; +export 'core/bdd_engine.dart'; diff --git a/lib/src/bdd_base.dart b/lib/src/core/bdd_base.dart similarity index 71% rename from lib/src/bdd_base.dart rename to lib/src/core/bdd_base.dart index 8c49167..e66a228 100644 --- a/lib/src/bdd_base.dart +++ b/lib/src/core/bdd_base.dart @@ -1,15 +1,13 @@ -import 'dart:async'; import 'dart:core'; import 'dart:io'; import 'dart:math'; import 'package:characters/characters.dart'; import 'package:collection/collection.dart'; -import 'package:flutter/foundation.dart'; -import "package:flutter_test/flutter_test.dart"; import "package:meta/meta.dart"; import 'bdd_context.dart'; +import 'bdd_engine.dart'; /// This interface helps to format values in Examples and Tables. /// If a value implements the [BddDescribe] interface, or if it has a @@ -41,24 +39,7 @@ class row { val? v14, val? v15, val? v16, - ]) : values = [ - v1, - v2, - v3, - v4, - v5, - v6, - v7, - v8, - v9, - v10, - v11, - v12, - v13, - v14, - v15, - v16 - ].whereNotNull().toList(); + ]) : values = [v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16].nonNulls.toList(); } class val { @@ -128,17 +109,7 @@ class BddKeywords { static const empty = const BddKeywords.only(); - final String feature, - scenario, - scenarioOutline, - given, - when, - then, - and, - but, - comment, - examples, - table; + final String feature, scenario, scenarioOutline, given, when, then, and, but, comment, examples, table; } class BddConfig { @@ -202,6 +173,27 @@ abstract class _BaseTerm { enum _Variation { term, and, but, note } +/// A mixin to indicate that a BddTerm can act as a trigger to run the test. +mixin BddRunnable on BddTerm {} + +mixin BddCodeable on BddTerm { + /// Internal method to register the code block execution logic. + /// Used by runner extensions to supply properly typed contexts. + void addCode(CodeRun codeRun) { + if (this is BddGiven) { + _GivenCode(bdd, codeRun); + } else if (this is BddWhen) { + _WhenCode(bdd, codeRun); + } else if (this is BddThen) { + _ThenCode(bdd, codeRun); + } else if (this is BddExample) { + _ExampleCode(bdd, codeRun); + } else { + throw UnsupportedError('Unsupported BddTerm for code: $runtimeType'); + } + } +} + abstract class BddTerm extends _BaseTerm { // final String text; @@ -229,23 +221,21 @@ abstract class BddTerm extends _BaseTerm { ? config.keywords.comment : null; - String? _keywordPrefixVariation(BddConfig config) => - (variation == _Variation.and) - ? config.keywordPrefix.and - : (variation == _Variation.but) - ? config.keywordPrefix.but - : (variation == _Variation.note) - ? config.keywordPrefix.comment - : null; - - String? _keywordSuffixVariation(BddConfig config) => - (variation == _Variation.and) - ? config.keywordSuffix.and - : (variation == _Variation.but) - ? config.keywordSuffix.but - : (variation == _Variation.note) - ? config.keywordSuffix.comment - : null; + String? _keywordPrefixVariation(BddConfig config) => (variation == _Variation.and) + ? config.keywordPrefix.and + : (variation == _Variation.but) + ? config.keywordPrefix.but + : (variation == _Variation.note) + ? config.keywordPrefix.comment + : null; + + String? _keywordSuffixVariation(BddConfig config) => (variation == _Variation.and) + ? config.keywordSuffix.and + : (variation == _Variation.but) + ? config.keywordSuffix.but + : (variation == _Variation.note) + ? config.keywordSuffix.comment + : null; String? _prefixVariation(BddConfig config) => (variation == _Variation.and) ? config.prefix.and @@ -309,20 +299,35 @@ abstract class BddTerm extends _BaseTerm { @override String toString([BddConfig config = BddConfig._default]) => - keywordPrefix(config) + - spaces(config) + - _keyword(config) + - keywordSuffix(config) + - ' ' + - prefix(config) + - _capitalize(text) + - suffix(config); + keywordPrefix(config) + spaces(config) + _keyword(config) + keywordSuffix(config) + ' ' + prefix(config) + _capitalize(text) + suffix(config); } -abstract class BddCodeTerm extends _BaseTerm { +abstract class BddCodeTerm extends BddTerm { final CodeRun codeRun; - BddCodeTerm(BddFramework bdd, this.codeRun) : super(bdd); + BddCodeTerm(BddFramework bdd, this.codeRun) : super(bdd, '', _Variation.term); + + // We don't need to output code terms to the feature file + @override + String toString([BddConfig config = BddConfig._default]) => ''; + + @override + String keyword(BddConfig config) => ''; + + @override + String keywordPrefix(BddConfig config) => ''; + + @override + String keywordSuffix(BddConfig config) => ''; + + @override + String prefix(BddConfig config) => ''; + + @override + String spaces(BddConfig config) => ''; + + @override + String suffix(BddConfig config) => ''; } class BddFramework { @@ -330,19 +335,23 @@ class BddFramework { final BddFeature? feature; final List<_BaseTerm> terms; Duration? _timeout; + Duration? get internalTimeout => _timeout; TestRunConfig? _config; - bool _skip; + TestRunConfig? get internalConfig => _config; + + bool _skip = false; + bool get internalSkip => _skip; + final List codeRuns; /// Nulls means the test was not run yet. /// True means it passed. /// False means it did not pass. - List passed; + final List passed; - BddFramework([this.feature]) - : terms = [], - _timeout = null, - _skip = false, + BddFramework([ + this.feature, + ]) : terms = [], codeRuns = [], passed = []; @@ -359,13 +368,12 @@ class BddFramework { /// A Bdd may have 0, 1, or more tables (which are not examples). List tables() => - // TODO: This was refactored, and BddExample is not of type BddTableTerm anymore. - // TODO: Can remove the where. + // TODO: This was refactored, and BddExample is not of type BddTableTerm anymore. + // TODO: Can remove the where. allTerms().where((t) => t is! BddExample).toList(); /// The example, if it exists, may have any number of rows. - Set? exampleRow(int? count) => - (count == null) ? null : example()?.rows[count]; + Set? exampleRow(int? count) => (count == null) ? null : example()?.rows[count]; int numberOfExamples() { BddExample? _example = example(); @@ -417,7 +425,7 @@ class BddFramework { /// "Given I am logged into the website" sets the scene for the actions that follow. BddGiven given(String text) => BddGiven(this, text); - Iterable get textTerms => terms.whereType(); + Iterable get textTerms => terms.whereType().where((t) => t is! BddCodeTerm); Iterable get codeTerms => terms.whereType(); @@ -436,14 +444,16 @@ class BddFramework { BddConfig config = BddConfig._default, bool withFeature = false, }) => - (withFeature ? feature?.toString(config) ?? "" : "") + - toMap(config).join(config.endOfLineChar) + - config.endOfLineChar; + (withFeature ? feature?.toString(config) ?? "" : "") + toMap(config).join(config.endOfLineChar) + config.endOfLineChar; + + @visibleForTesting + void testAddThenCode(CodeRun code) { + _ThenCode(this, code); + } } class BddScenario extends BddTerm { - BddScenario(BddFramework bdd, String text) - : super(bdd, text, _Variation.term); + BddScenario(BddFramework bdd, String text) : super(bdd, text, _Variation.term); bool get containsExample => bdd.terms.any((term) => term is BddExample); @@ -491,38 +501,31 @@ class BddScenario extends BddTerm { @override // ignore: unnecessary_overrides - String toString([BddConfig config = BddConfig._default]) => - super.toString(config); + String toString([BddConfig config = BddConfig._default]) => super.toString(config); } -class BddGiven extends BddTerm { +class BddGiven extends BddTerm with BddCodeable, BddRunnable { BddGiven(BddFramework bdd, String text) : super(bdd, text, _Variation.term); - BddGiven._(BddFramework bdd, String text, _Variation variation) - : super(bdd, text, variation); + BddGiven._(BddFramework bdd, String text, _Variation variation) : super(bdd, text, variation); @override String spaces(BddConfig config) => config.spaces + config.spaces; @override - String keyword(BddConfig config) => - _keywordVariation(config) ?? config.keywords.given; + String keyword(BddConfig config) => _keywordVariation(config) ?? config.keywords.given; @override - String keywordPrefix(BddConfig config) => - _keywordPrefixVariation(config) ?? config.keywordPrefix.given; + String keywordPrefix(BddConfig config) => _keywordPrefixVariation(config) ?? config.keywordPrefix.given; @override - String keywordSuffix(BddConfig config) => - _keywordSuffixVariation(config) ?? config.keywordSuffix.given; + String keywordSuffix(BddConfig config) => _keywordSuffixVariation(config) ?? config.keywordSuffix.given; @override - String prefix(BddConfig config) => - _prefixVariation(config) ?? config.prefix.given; + String prefix(BddConfig config) => _prefixVariation(config) ?? config.prefix.given; @override - String suffix(BddConfig config) => - _suffixVariation(config) ?? config.suffix.given; + String suffix(BddConfig config) => _suffixVariation(config) ?? config.suffix.given; /// A table must have a name and rows. The name is necessary if you want to /// read the values from it later (if not, just pass an empty string). @@ -546,8 +549,7 @@ class BddGiven extends BddTerm { row? row15, row? row16, ]) => - BddGivenTable(bdd, tableName, row1, row2, row3, row4, row5, row6, row7, - row8, row9, row10, row11, row12, row13, row14, row15, row16); + BddGivenTable(bdd, tableName, row1, row2, row3, row4, row5, row6, row7, row8, row9, row10, row11, row12, row13, row14, row15, row16); /// This keyword is used to extend a 'Given', 'When', or 'Then' step. /// It allows you to add multiple conditions or actions in the same step @@ -585,23 +587,18 @@ class BddGiven extends BddTerm { /// An example is, "Then I should be redirected to the dashboard". BddThen then(String text) => BddThen(bdd, text); - _GivenCode code(CodeRun code) => _GivenCode(bdd, code); - @override // ignore: unnecessary_overrides - String toString([BddConfig config = BddConfig._default]) => - super.toString(config); + String toString([BddConfig config = BddConfig._default]) => super.toString(config); } -class _GivenCode extends BddCodeTerm { +class _GivenCode extends BddCodeTerm with BddCodeable<_GivenCode>, BddRunnable { _GivenCode(BddFramework bdd, CodeRun code) : super(bdd, code); /// A table must have a name and rows. The name is necessary if you want to /// read the values from it later (if not, just pass an empty string). /// Example: `ctx.table('notifications').row(0).val('read') as bool`. - BddGivenTable table(String tableName, row row1, - [row? row2, row? row3, row? row4]) => - BddGivenTable(bdd, tableName, row1, row2, row3, row4); + BddGivenTable table(String tableName, row row1, [row? row2, row? row3, row? row4]) => BddGivenTable(bdd, tableName, row1, row2, row3, row4); /// This keyword is used to extend a 'Given', 'When', or 'Then' step. /// It allows you to add multiple conditions or actions in the same step @@ -629,48 +626,36 @@ class _GivenCode extends BddCodeTerm { /// This keyword indicates the specific action taken by the user or the system. /// It's the trigger for the behavior that you're specifying. For instance, - /// "When I click the 'Submit' button" describes the action taken after the - /// initial context is set by the 'Given' step. BddWhen when(String text) => BddWhen(bdd, text); - - _GivenCode code(CodeRun code) => _GivenCode(bdd, code); } -class BddWhen extends BddTerm { +class BddWhen extends BddTerm with BddCodeable, BddRunnable { BddWhen(BddFramework bdd, String text) : super(bdd, text, _Variation.term); - BddWhen._(BddFramework bdd, String text, _Variation variation) - : super(bdd, text, variation); + BddWhen._(BddFramework bdd, String text, _Variation variation) : super(bdd, text, variation); @override String spaces(BddConfig config) => config.spaces + config.spaces; @override - String keyword(BddConfig config) => - _keywordVariation(config) ?? config.keywords.when; + String keyword(BddConfig config) => _keywordVariation(config) ?? config.keywords.when; @override - String keywordPrefix(BddConfig config) => - _keywordPrefixVariation(config) ?? config.keywordPrefix.when; + String keywordPrefix(BddConfig config) => _keywordPrefixVariation(config) ?? config.keywordPrefix.when; @override - String keywordSuffix(BddConfig config) => - _keywordSuffixVariation(config) ?? config.keywordSuffix.when; + String keywordSuffix(BddConfig config) => _keywordSuffixVariation(config) ?? config.keywordSuffix.when; @override - String prefix(BddConfig config) => - _prefixVariation(config) ?? config.prefix.when; + String prefix(BddConfig config) => _prefixVariation(config) ?? config.prefix.when; @override - String suffix(BddConfig config) => - _suffixVariation(config) ?? config.suffix.when; + String suffix(BddConfig config) => _suffixVariation(config) ?? config.suffix.when; /// A table must have a name and rows. The name is necessary if you want to /// read the values from it later (if not, just pass an empty string). /// Example: `ctx.table('notifications').row(0).val('read') as bool`. - BddWhenTable table(String tableName, row row1, - [row? row2, row? row3, row? row4]) => - BddWhenTable(bdd, tableName, row1, row2, row3, row4); + BddWhenTable table(String tableName, row row1, [row? row2, row? row3, row? row4]) => BddWhenTable(bdd, tableName, row1, row2, row3, row4); /// This keyword is used to extend a 'Given', 'When', or 'Then' step. /// It allows you to add multiple conditions or actions in the same step @@ -702,26 +687,19 @@ class BddWhen extends BddTerm { /// An example is, "Then I should be redirected to the dashboard". BddThen then(String text) => BddThen(bdd, text); - _WhenCode code(CodeRun code) => _WhenCode(bdd, code); - - /// Should be used to actually provide the code that runs the BDD. - void run(CodeRun code) => _Run().run(bdd, code); - @override + // ignore: unnecessary_overrides - String toString([BddConfig config = BddConfig._default]) => - super.toString(config); + String toString([BddConfig config = BddConfig._default]) => super.toString(config); } -class _WhenCode extends BddCodeTerm { +class _WhenCode extends BddCodeTerm with BddCodeable<_WhenCode>, BddRunnable { _WhenCode(BddFramework bdd, CodeRun code) : super(bdd, code); /// A table must have a name and rows. The name is necessary if you want to /// read the values from it later (if not, just pass an empty string). /// Example: `ctx.table('notifications').row(0).val('read') as bool`. - BddWhenTable table(String tableName, row row1, - [row? row2, row? row3, row? row4]) => - BddWhenTable(bdd, tableName, row1, row2, row3, row4); + BddWhenTable table(String tableName, row row1, [row? row2, row? row3, row? row4]) => BddWhenTable(bdd, tableName, row1, row2, row3, row4); /// This keyword is used to extend a 'Given', 'When', or 'Then' step. /// It allows you to add multiple conditions or actions in the same step @@ -748,49 +726,36 @@ class _WhenCode extends BddCodeTerm { BddWhen note(String text) => BddWhen._(bdd, text, _Variation.note); /// This keyword is used to describe the expected outcome or result after the - /// 'When' step is executed. It's used to assert that a certain outcome should - /// occur, which helps to validate whether the system behaves as expected. - /// An example is, "Then I should be redirected to the dashboard". BddThen then(String text) => BddThen(bdd, text); - - _WhenCode code(CodeRun code) => _WhenCode(bdd, code); } -class BddThen extends BddTerm { +class BddThen extends BddTerm with BddCodeable, BddRunnable { BddThen(BddFramework bdd, String text) : super(bdd, text, _Variation.term); - BddThen._(BddFramework bdd, String text, _Variation variation) - : super(bdd, text, variation); + BddThen._(BddFramework bdd, String text, _Variation variation) : super(bdd, text, variation); @override String spaces(BddConfig config) => config.spaces + config.spaces; @override - String keyword(BddConfig config) => - _keywordVariation(config) ?? config.keywords.then; + String keyword(BddConfig config) => _keywordVariation(config) ?? config.keywords.then; @override - String keywordPrefix(BddConfig config) => - _keywordPrefixVariation(config) ?? config.keywordPrefix.then; + String keywordPrefix(BddConfig config) => _keywordPrefixVariation(config) ?? config.keywordPrefix.then; @override - String keywordSuffix(BddConfig config) => - _keywordSuffixVariation(config) ?? config.keywordSuffix.then; + String keywordSuffix(BddConfig config) => _keywordSuffixVariation(config) ?? config.keywordSuffix.then; @override - String prefix(BddConfig config) => - _prefixVariation(config) ?? config.prefix.then; + String prefix(BddConfig config) => _prefixVariation(config) ?? config.prefix.then; @override - String suffix(BddConfig config) => - _suffixVariation(config) ?? config.suffix.then; + String suffix(BddConfig config) => _suffixVariation(config) ?? config.suffix.then; /// A table must have a name and rows. The name is necessary if you want to /// read the values from it later (if not, just pass an empty string). /// Example: `ctx.table('notifications').row(0).val('read') as bool`. - BddThenTable table(String tableName, row row1, - [row? row2, row? row3, row? row4]) => - BddThenTable(bdd, tableName, row1, row2, row3, row4); + BddThenTable table(String tableName, row row1, [row? row2, row? row3, row? row4]) => BddThenTable(bdd, tableName, row1, row2, row3, row4); /// This keyword is used to extend a 'Given', 'When', or 'Then' step. /// It allows you to add multiple conditions or actions in the same step @@ -888,35 +853,27 @@ class BddThen extends BddTerm { val? v14, val? v15, ]) => - BddExample(bdd, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, - v14, v15); - - _ThenCode code(CodeRun code) => _ThenCode(bdd, code); - - /// Should be used to actually provide the code that runs the BDD. - void run(CodeRun code) => _Run().run(bdd, code); + BddExample(bdd, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15); @visibleForTesting BddFramework testRun(CodeRun code, BddReporter reporter) { - _TestRun(code, reporter).run(bdd); + // ignore: invalid_use_of_visible_for_testing_member + TestBddEngine(code, reporter).run(bdd); return bdd; } @override // ignore: unnecessary_overrides - String toString([BddConfig config = BddConfig._default]) => - super.toString(config); + String toString([BddConfig config = BddConfig._default]) => super.toString(config); } -class _ThenCode extends BddCodeTerm { +class _ThenCode extends BddCodeTerm with BddCodeable<_ThenCode>, BddRunnable { _ThenCode(BddFramework bdd, CodeRun code) : super(bdd, code); /// A table must have a name and rows. The name is necessary if you want to /// read the values from it later (if not, just pass an empty string). /// Example: `ctx.table('notifications').row(0).val('read') as bool`. - BddThenTable table(String tableName, row row1, - [row? row2, row? row3, row? row4]) => - BddThenTable(bdd, tableName, row1, row2, row3, row4); + BddThenTable table(String tableName, row row1, [row? row2, row? row3, row? row4]) => BddThenTable(bdd, tableName, row1, row2, row3, row4); /// Examples are used in the context of Scenario Outlines. A Scenario Outline /// is a template for multiple tests, and the "Examples" section provides @@ -990,8 +947,7 @@ class _ThenCode extends BddCodeTerm { val? v14, val? v15, ]) => - BddExample(bdd, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, - v14, v15); + BddExample(bdd, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15); /// This keyword is used to extend a 'Given', 'When', or 'Then' step. /// It allows you to add multiple conditions or actions in the same step @@ -1017,14 +973,10 @@ class _ThenCode extends BddCodeTerm { /// making it easier for others to understand the purpose and scope of the test. BddWhen note(String text) => BddWhen._(bdd, text, _Variation.note); - _ThenCode code(CodeRun code) => _ThenCode(bdd, code); - - /// Should be used to actually provide the code that runs the BDD. - void run(CodeRun code) => _Run().run(bdd, code); - @visibleForTesting BddFramework testRun(CodeRun code, BddReporter reporter) { - _TestRun(code, reporter).run(bdd); + // ignore: invalid_use_of_visible_for_testing_member + TestBddEngine(code, reporter).run(bdd); return bdd; } } @@ -1035,11 +987,9 @@ abstract class BddTableTerm extends BddTerm { final List rows = []; - BddTableTerm(BddFramework bdd, this.tableName) - : super(bdd, '', _Variation.term); + BddTableTerm(BddFramework bdd, this.tableName) : super(bdd, '', _Variation.term); - /// Should be used to actually provide the code that runs the BDD. - void run(CodeRun code) => _Run().run(bdd, code); + // Decoupled run /// Here we have something like: /// [ @@ -1054,8 +1004,7 @@ abstract class BddTableTerm extends BddTerm { for (val value in _row.values) { int? maxValue1 = sizes[value.name]; int maxValue2 = max(value.name.length, value.toString(config).length); - int maxValue = - (maxValue1 == null) ? maxValue2 : max(maxValue1, maxValue2); + int maxValue = (maxValue1 == null) ? maxValue2 : max(maxValue1, maxValue2); sizes[value.name] = maxValue; } @@ -1066,10 +1015,7 @@ abstract class BddTableTerm extends BddTerm { var endOfLineChar = config.endOfLineChar; var tableDivider = config.tableDivider; - String rightAlignPadding = spaces + - spaces + - spaces + - ((config.rightAlignKeywords) ? config.padChar * 4 : ''); + String rightAlignPadding = spaces + spaces + spaces + ((config.rightAlignKeywords) ? config.padChar * 4 : ''); String header = rightAlignPadding + '$tableDivider$space' + @@ -1116,44 +1062,21 @@ abstract class BddTableTerm extends BddTerm { /// Tables have a special toString treatment. @override String toString([BddConfig config = BddConfig._default]) => - keywordPrefix(config) + - keyword(config) + - keywordSuffix(config) + - prefix(config) + - formatTable(config) + - suffix(config); + keywordPrefix(config) + keyword(config) + keywordSuffix(config) + prefix(config) + formatTable(config) + suffix(config); } -class BddExample extends BddTerm { +class BddExample extends BddTerm with BddCodeable, BddRunnable { // - BddExample( - BddFramework bdd, - val v1, - val? v2, - val? v3, - val? v4, - val? v5, - val? v6, - val? v7, - val? v8, - val? v9, - val? v10, - val? v11, - val? v12, - val? v13, - val? v14, + BddExample(BddFramework bdd, val v1, val? v2, val? v3, val? v4, val? v5, val? v6, val? v7, val? v8, val? v9, val? v10, val? v11, val? v12, val? v13, val? v14, val? v15) : super(bdd, '', _Variation.term) { - var set = [v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15] - .whereNotNull() - .toSet(); + var set = [v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15].nonNulls.toSet(); rows.add(set); } final List> rows = []; - /// Should be used to actually provide the code that runs the BDD. - void run(CodeRun code) => _Run().run(bdd, code); + // Decoupled run /// Here we have something like: /// [ @@ -1168,8 +1091,7 @@ class BddExample extends BddTerm { for (val value in row) { int? maxValue1 = sizes[value.name]; int maxValue2 = max(value.name.length, value.toString(config).length); - int maxValue = - (maxValue1 == null) ? maxValue2 : max(maxValue1, maxValue2); + int maxValue = (maxValue1 == null) ? maxValue2 : max(maxValue1, maxValue2); sizes[value.name] = maxValue; } @@ -1180,10 +1102,7 @@ class BddExample extends BddTerm { var endOfLineChar = config.endOfLineChar; var tableDivider = config.tableDivider; - String rightAlignPadding = spaces + - spaces + - spaces + - ((config.rightAlignKeywords) ? config.padChar * 4 : ''); + String rightAlignPadding = spaces + spaces + spaces + ((config.rightAlignKeywords) ? config.padChar * 4 : ''); String header = rightAlignPadding + '$tableDivider$space' + @@ -1242,24 +1161,19 @@ class BddExample extends BddTerm { String spaces(BddConfig config) => config.spaces + config.spaces; @override - String keyword(BddConfig config) => - _keywordVariation(config) ?? config.keywords.examples; + String keyword(BddConfig config) => _keywordVariation(config) ?? config.keywords.examples; @override - String keywordPrefix(BddConfig config) => - _keywordPrefixVariation(config) ?? config.keywordPrefix.examples; + String keywordPrefix(BddConfig config) => _keywordPrefixVariation(config) ?? config.keywordPrefix.examples; @override - String keywordSuffix(BddConfig config) => - _keywordSuffixVariation(config) ?? config.keywordSuffix.examples; + String keywordSuffix(BddConfig config) => _keywordSuffixVariation(config) ?? config.keywordSuffix.examples; @override - String prefix(BddConfig config) => - _prefixVariation(config) ?? config.prefix.examples; + String prefix(BddConfig config) => _prefixVariation(config) ?? config.prefix.examples; @override - String suffix(BddConfig config) => - _suffixVariation(config) ?? config.suffix.examples; + String suffix(BddConfig config) => _suffixVariation(config) ?? config.suffix.examples; /// Examples are used in the context of Scenario Outlines. A Scenario Outline /// is a template for multiple tests, and the "Examples" section provides @@ -1333,15 +1247,14 @@ class BddExample extends BddTerm { val? v14, val? v15, ]) { - rows.add([v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15] - .whereNotNull() - .toSet()); + rows.add([v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15].nonNulls.toSet()); return this; } @visibleForTesting BddFramework testRun(CodeRun code, BddReporter reporter) { - _TestRun(code, reporter).run(bdd); + // ignore: invalid_use_of_visible_for_testing_member + TestBddEngine(code, reporter).run(bdd); return bdd; } @@ -1381,24 +1294,7 @@ class BddGivenTable extends BddTableTerm { row? row15, row? row16, ]) : super(bdd, tableName) { - rows.addAll([ - row1, - row2, - row3, - row4, - row5, - row6, - row7, - row8, - row9, - row10, - row11, - row12, - row13, - row14, - row15, - row16 - ].whereNotNull()); + rows.addAll([row1, row2, row3, row4, row5, row6, row7, row8, row9, row10, row11, row12, row13, row14, row15, row16].nonNulls); } /// This keyword is used to extend a 'Given', 'When', or 'Then' step. @@ -1431,20 +1327,15 @@ class BddGivenTable extends BddTableTerm { /// initial context is set by the 'Given' step. BddWhen when(String text) => BddWhen(bdd, text); - _GivenCode code(CodeRun code) => _GivenCode(bdd, code); - @override // ignore: unnecessary_overrides - String toString([BddConfig config = BddConfig._default]) => - super.toString(config); + String toString([BddConfig config = BddConfig._default]) => super.toString(config); } -class BddWhenTable extends BddTableTerm { +class BddWhenTable extends BddTableTerm with BddRunnable { // - BddWhenTable(BddFramework bdd, String tableName, row row1, - [row? row2, row? row3, row? row4]) - : super(bdd, tableName) { - rows.addAll([row1, row2, row3, row4].whereNotNull()); + BddWhenTable(BddFramework bdd, String tableName, row row1, [row? row2, row? row3, row? row4]) : super(bdd, tableName) { + rows.addAll([row1, row2, row3, row4].nonNulls); } /// This keyword is used to extend a 'Given', 'When', or 'Then' step. @@ -1477,20 +1368,15 @@ class BddWhenTable extends BddTableTerm { /// An example is, "Then I should be redirected to the dashboard". BddThen then(String text) => BddThen(bdd, text); - _WhenCode code(CodeRun code) => _WhenCode(bdd, code); - @override // ignore: unnecessary_overrides - String toString([BddConfig config = BddConfig._default]) => - super.toString(config); + String toString([BddConfig config = BddConfig._default]) => super.toString(config); } -class BddThenTable extends BddTableTerm { +class BddThenTable extends BddTableTerm with BddRunnable { // - BddThenTable(BddFramework bdd, String tableName, row row1, - [row? row2, row? row3, row? row4]) - : super(bdd, tableName) { - rows.addAll([row1, row2, row3, row4].whereNotNull()); + BddThenTable(BddFramework bdd, String tableName, row row1, [row? row2, row? row3, row? row4]) : super(bdd, tableName) { + rows.addAll([row1, row2, row3, row4].nonNulls); } /// This keyword is used to extend a 'Given', 'When', or 'Then' step. @@ -1589,23 +1475,16 @@ class BddThenTable extends BddTableTerm { val? v14, val? v15, ]) => - BddExample(bdd, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, - v14, v15); - - _ThenCode code(CodeRun code) => _ThenCode(bdd, code); + BddExample(bdd, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15); @override // ignore: unnecessary_overrides - String toString([BddConfig config = BddConfig._default]) => - super.toString(config); - - /// Should be used to actually provide the code that runs the BDD. - @override - void run(CodeRun code) => _Run().run(bdd, code); + String toString([BddConfig config = BddConfig._default]) => super.toString(config); @visibleForTesting BddFramework testRun(CodeRun code, BddReporter reporter) { - _TestRun(code, reporter).run(bdd); + // ignore: invalid_use_of_visible_for_testing_member + TestBddEngine(code, reporter).run(bdd); return bdd; } } @@ -1617,12 +1496,10 @@ class TestResult { Iterable get terms => _bdd.textTerms; - List toMap([BddConfig config = BddConfig._default]) => - _bdd.toMap(config); + List toMap([BddConfig config = BddConfig._default]) => _bdd.toMap(config); @override - String toString([BddConfig config = BddConfig._default]) => - _bdd.toString(config: config); + String toString([BddConfig config = BddConfig._default]) => _bdd.toString(config: config); bool get wasSkipped => _bdd._skip; @@ -1650,8 +1527,7 @@ class BddFeature { BddFeature(this.title, {this.description}) : _bdds = []; - List get testResults => - _bdds.map((bdd) => TestResult(bdd)).toList(); + List get testResults => _bdds.map((bdd) => TestResult(bdd)).toList(); List result = []; @@ -1672,23 +1548,14 @@ class BddFeature { if (description != null) { var parts = description!.trim().split('\n'); - result = result + - config.spaces + - config.prefix.feature + - parts.join(config.endOfLineChar + config.spaces) + - config.suffix.feature + - config.endOfLineChar; + result = result + config.spaces + config.prefix.feature + parts.join(config.endOfLineChar + config.spaces) + config.suffix.feature + config.endOfLineChar; } return result; } @override - bool operator ==(Object other) => - identical(this, other) || - other is BddFeature && - runtimeType == other.runtimeType && - title == other.title; + bool operator ==(Object other) => identical(this, other) || other is BddFeature && runtimeType == other.runtimeType && title == other.title; @override int get hashCode => title.hashCode; @@ -1712,8 +1579,8 @@ abstract class BddReporter { static _RunInfo runInfo = _RunInfo(); static final _emptyFeature = BddFeature(""); - static final List _reporters = []; - static bool ignoreOverflow = true; + static final List reporters = []; + // static bool ignoreOverflow = true; static const yellow = "\x1B[38;5;226m"; static const reset = "\u001b[0m"; @@ -1724,42 +1591,53 @@ abstract class BddReporter { BddReporter? r4, BddReporter? r5, ]) { - _reporters + reporters ..clear() - ..addAll([r1, r2, r3, r4, r5].whereNotNull()); + ..addAll([r1, r2, r3, r4, r5].nonNulls); + } + + static void Function(dynamic Function())? _customTearDownAll; + + /// Used to inject a custom tearDownAll implementation (like the one from `test` or `flutter_test`). + /// If not set, [reportAll] will not use `tearDownAll` and just run immediately. + static void setTearDownAll(void Function(dynamic Function()) func) { + _customTearDownAll = func; } static Future reportAll() async { - tearDownAll(() async { + final reportLogic = () async { stdout.writeln(yellow); stdout.writeln('RESULTS ══════════════════════════════════════════════'); - stdout.writeln( - 'TOTAL: ${runInfo.totalTestCount} tests (${runInfo.testCount} BDDs)'); + stdout.writeln('TOTAL: ${runInfo.totalTestCount} tests (${runInfo.testCount} BDDs)'); stdout.writeln('PASSED: ${runInfo.passedCount} tests'); stdout.writeln('FAILED: ${runInfo.failedCount} tests'); stdout.writeln('SKIPPED: ${runInfo.skipCount} tests'); - stdout.writeln( - '══════════════════════════════════════════════════════$reset'); + stdout.writeln('══════════════════════════════════════════════════════$reset'); stdout.writeln('\n'); - for (BddReporter _reporter in BddReporter._reporters) { + for (BddReporter _reporter in BddReporter.reporters) { stdout.writeln('Running the ${_reporter.runtimeType}...\n'); await _reporter.report(); } - }); + }; + + if (_customTearDownAll != null) { + _customTearDownAll!(reportLogic); + } else { + await reportLogic(); + } } final Set features = {}; - void _addBdd(BddFramework bdd) { + void addBdd(BddFramework bdd) { // // Use the feature, if provided. Otherwise, use the "empty feature". var _feature = bdd.feature ?? _emptyFeature; // We must find out if we already have a feature with the given title. // If we do, use the one we already have. - BddFeature? feature = - features.firstWhereOrNull((feature) => feature.title == _feature.title); + BddFeature? feature = features.firstWhereOrNull((feature) => feature.title == _feature.title); // If we don't, use the new one provided, and put it in the features set. if (feature == null) { @@ -1773,20 +1651,17 @@ abstract class BddReporter { /// Keeps A-Z 0-9, make it lowercase, and change spaces into underline. String normalizeFileName(String name) => - name.trim().splitMapJoin(RegExp(r"""[ "'#<$+%>!`&*|{?=}/:\\@^.]"""), - onMatch: (m) => m[0] == ' ' ? '_' : '', - onNonMatch: (m) => m.toLowerCase()); + name.trim().splitMapJoin(RegExp(r"""[ "'#<$+%>!`&*|{?=}/:\\@^.]"""), onMatch: (m) => m[0] == ' ' ? '_' : '', onNonMatch: (m) => m.toLowerCase()); } class TestRunConfig { final String? testOn; - final Timeout? timeout; + final dynamic timeout; final dynamic tags; final Map? onPlatform; final int? retry; - TestRunConfig( - {this.testOn, this.timeout, this.tags, this.onPlatform, this.retry}); + TestRunConfig({this.testOn, this.timeout, this.tags, this.onPlatform, this.retry}); } class _RunInfo { @@ -1799,306 +1674,31 @@ class _RunInfo { bool overflowIsSetUp = false; } -typedef CodeRun = FutureOr Function(BddContext ctx)?; - -/// This will run with the global reporter/runInfo. -class _Run { - // - void run(BddFramework bdd, CodeRun code) { - // - // Add the code to the BDD, as a ThenCode. - _ThenCode(bdd, code); - - BddReporter._reporters.forEach((_reporter) { - _reporter._addBdd(bdd); - }); +// Runners moved to bdd_engine.dart - int numberOfExamples = bdd.numberOfExamples(); +class _ExampleCode extends BddCodeTerm with BddCodeable<_ExampleCode>, BddRunnable { + _ExampleCode(BddFramework bdd, CodeRun code) : super(bdd, code); - BddReporter.runInfo.testCount++; - - if (numberOfExamples == 0) - _runTheTest(bdd, null); - else { - for (int i = 0; i < numberOfExamples; i++) _runTheTest(bdd, i); - } - } - - static const BddConfig config = BddConfig( - // - keywords: BddKeywords( - feature: '${boldItalic}Feature:$boldItalicOff', - scenario: '${boldItalic}Scenario:$boldItalicOff', - scenarioOutline: '${boldItalic}Scenario Outline:$boldItalicOff', - given: '${boldItalic}Given$boldItalicOff', - when: '${boldItalic}When$boldItalicOff', - then: '${boldItalic}Then$boldItalicOff', - and: '${boldItalic}And$boldItalicOff', - but: '${boldItalic}But$boldItalicOff', - comment: '$boldItalic#$boldItalicOff', - examples: '${boldItalic}Examples:$boldItalicOff', - ), - // - keywordPrefix: BddKeywords.only( - scenario: '\n', - scenarioOutline: '\n', - given: '\n', - when: '\n', - then: '\n', - examples: '\n', - comment: grey, - ), - // - suffix: BddKeywords.only( - comment: blue, - ), - // - ); - - String subscript(int index) { - String result = ''; - var x = index.toString(); - for (int i = 0; i < x.length; i++) { - var char = x[i]; - result += { - '0': '₀', - '1': '₁', - '2': '₂', - '3': '₃', - '4': '₄', - '5': '₅', - '6': '₆', - '7': '₇', - '8': '₈', - '9': '₉' - }[char]!; - } - return result; - } - - /// Returns something like: "4₁₂" - String testCountStr(int testCount, int? exampleNumber) => - "$testCount${exampleNumber == null ? '' : '${subscript(exampleNumber + 1)}'}"; - - /// If the Bdd has examples, this method will be called once for each example, with - /// [exampleNumber] starting in 0. - /// - /// If the Bdd does NOT have examples, this method will run once, with [exampleNumber] null. - /// - void _runTheTest(BddFramework bdd, int? exampleNumber) { - // - BddReporter.runInfo.totalTestCount++; - - var totalRetries = bdd._config?.retry ?? 0; - var currentExecution = 0; - - String bddStr = bdd.toString(config: config, withFeature: true); - - int testCount = BddReporter.runInfo.testCount; - // int totalTestCount = runInfo.totalTestCount; - - if (bdd._skip) BddReporter.runInfo.skipCount++; - - String _testCountStr = testCountStr(testCount, exampleNumber); - - test( - // - '$_testCountStr ${bdd.description()}', - // - () async { - if (BddReporter.ignoreOverflow) _ignoreOverflowErrors(); - - currentExecution++; - - print((currentExecution == 1) // - ? "${_header(bdd._skip, _testCountStr)}$blue$bddStr$boldOff" - : "\n${red}Retry $currentExecution.\n$boldOff"); - - final example = BddTableValues.from(bdd.exampleRow(exampleNumber)); - final tables = BddMultipleTableValues.from(bdd.tables()); - final ctx = BddContext(example, tables); - - try { - // Run all bdd code. - for (CodeRun codeRun in bdd.codeTerms - .map((BddCodeTerm codeTerm) => codeTerm.codeRun)) { - await codeRun?.call(ctx); - } - } - // - catch (error, stacktrace) { - bdd.passed.add(false); - BddReporter.runInfo.failedCount++; - print("\n"); - - var errorDetails = FlutterErrorDetails( - library: 'BDD Framework', - exception: error, - stack: stacktrace, - stackFilter: _stackFilter, - ); - - reportTestException(errorDetails, ""); - - print(_fail(_testCountStr)); - return; - } - // - finally { - _cleanTargetPlatformOverride(); - } - - bdd.passed.add(true); - BddReporter.runInfo.passedCount++; - print(_footer(_testCountStr)); - }, - // - timeout: Timeout(bdd._timeout), - skip: bdd._skip, - tags: bdd._config?.tags, - onPlatform: bdd._config?.onPlatform, - retry: totalRetries, - testOn: bdd._config?.testOn, - ); - } - - static Iterable _stackFilter(Iterable frames) { - // Removes the frames we are not interested in. - var filteredFrames = frames.where((frame) => - !frame.contains("package:matcher/") && - !frame.contains("package:flutter_test/src/widget_tester.dart") && - !frame.contains("package:bdd_framework/src/") && - !frame.contains("package:test_api/src/")); - - return FlutterError.defaultStackFilter(filteredFrames); - } - - // static const white = "\x1B[38;5;255m"; - // static const reversed = "\u001b[7m"; - static const red = "\x1B[38;5;9m"; - static const blue = "\x1B[38;5;45m"; - static const yellow = "\x1B[38;5;226m"; - static const grey = "\x1B[38;5;246m"; - static const bold = "\u001b[1m"; - static const italic = "\u001b[3m"; - static const boldItalic = bold + italic; - static const boldItalicOff = boldOff + italicOff; - static const boldOff = "\u001b[22m"; - static const italicOff = "\u001b[23m"; - static const reset = "\u001b[0m"; - - // See ANSI Colors here: https://pub.dev/packages/ansicolor - String _header(bool skip, String testNumberStr) { - return yellow + - italic + - "TEST $testNumberStr ${skip ? "SKIPPED" : ""} " - "$italicOff══════════════════════════════════════════════════$reset\n\n"; - } - - String _footer(String testNumberStr) => - grey + "\n✔ ${italic}TEST $testNumberStr PASSED!\n\n" + italicOff; - - String _fail(String testNumberStr) => - grey + "\n⚠ ${italic}TEST $testNumberStr FAILED!\n" + italicOff; -} - -/// This is for testing the BDD framework only. -class _TestRun { - final CodeRun code; - final BddReporter? reporter; - - @visibleForTesting - _TestRun(this.code, this.reporter); - - void run(BddFramework bdd) { - // - // Add the code to the BDD, as a ThenCode. - _ThenCode(bdd, code); - - reporter?._addBdd(bdd); - - int numberOfExamples = bdd.numberOfExamples(); - - if (numberOfExamples == 0) - _runTheTest(bdd, null); - else { - for (int i = 0; i < numberOfExamples; i++) _runTheTest(bdd, i); - } - } - - void _runTheTest(BddFramework bdd, int? exampleNumber) { - // - final example = BddTableValues.from(bdd.exampleRow(exampleNumber)); - final tables = BddMultipleTableValues.from(bdd.tables()); - final ctx = BddContext(example, tables); - - if (!bdd._skip) - try { - /// Run all bdd code. - Iterable codeRuns = - bdd.codeTerms.map((BddCodeTerm codeTerm) => codeTerm.codeRun); - for (CodeRun codeRun in codeRuns) { - codeRun?.call(ctx); - } - - bdd.passed.add(true); - } catch (error) { - bdd.passed.add(false); - } + BddExample example( + val v1, [ + val? v2, + val? v3, + val? v4, + val? v5, + val? v6, + val? v7, + val? v8, + val? v9, + val? v10, + val? v11, + val? v12, + val? v13, + val? v14, + val? v15, + ]) { + var example = bdd.example(); + if (example == null) throw StateError("Cannot add example properties without an existing example."); + example.example(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15); + return example; } } - -/// Overrides `FlutterError.onError` defined by `TestWidgetsFlutterBinding._runTest()`, -/// so that overflow errors are only printed to the console, and not considered test failures. -/// -/// This function should be called only by the `testWidgets()` body, -/// because only in this context `FlutterError.onError` is used to get exceptions. -/// -/// It's not necessary to reset the default value of `FlutterError.onError`, -/// because the `TestWidgetsFlutterBinding.postTest()` method does that already. -/// -/// See: https://stackoverflow.com/a/57501230/6696558 -/// -void _ignoreOverflowErrors() { - // - var handlerOriginal = FlutterError.onError; - - FlutterError.onError = (details) { - var exception = details.exception; - var ifOverflow = (exception is FlutterError) && - exception.diagnostics - .map((diagnostic) => diagnostic.value) - .whereType>() - .expand((value) => value) - .any((data) => - data.toString().startsWith("A RenderFlex overflowed by")); - - if (ifOverflow) - FlutterError.dumpErrorToConsole(details); - else - handlerOriginal!(details); - }; -} - -/// During the binding process that happens inside the `testWidgets()` function, -/// the `BindingBase.initServiceExtensions()` method determines, based on the -/// operating system, the value of `debugDefaultTargetPlatformOverride`. -/// -/// In common test situations, the operating system is Windows, causing -/// null to be assigned to `debugDefaultTargetPlatformOverride`. -/// -/// Inside `testWidgets()`, right after executing the `WidgetTesterCallback`, -/// the `TestWidgetsFlutterBinding.runTest()` method calls the function -/// `debugAssertAllFoundationVarsUnset()` which requires that -/// `debugDefaultTargetPlatformOverride` is null, because it expects the test -/// to be run on Windows. As this is not the case, an error is thrown. -/// -/// The easiest way to prevent this error from being thrown is by setting null -/// to the `debugDefaultTargetPlatformOverride` in the `WidgetTesterCallback` of the -/// `testWidgets()`. -/// -/// Same explanation, slightly different: -/// https://stackoverflow.com/a/57628196/6696558 -/// -void _cleanTargetPlatformOverride() => - (debugDefaultTargetPlatformOverride = null); diff --git a/lib/src/bdd_context.dart b/lib/src/core/bdd_context.dart similarity index 71% rename from lib/src/bdd_context.dart rename to lib/src/core/bdd_context.dart index 8bdea13..d8dc369 100644 --- a/lib/src/bdd_context.dart +++ b/lib/src/core/bdd_context.dart @@ -1,8 +1,11 @@ -import 'package:flutter/foundation.dart'; +import 'dart:async'; +import 'package:collection/collection.dart'; import 'bdd_base.dart' as bdd_framework show val; import 'bdd_base.dart' show BddTableTerm; +typedef CodeRun = FutureOr Function(BddContext ctx); + class BddContext { final BddTableValues example; @@ -14,11 +17,7 @@ class BddContext { @override bool operator ==(Object other) => - identical(this, other) || - other is BddContext && - runtimeType == other.runtimeType && - example == other.example && - _table == other._table; + identical(this, other) || other is BddContext && runtimeType == other.runtimeType && example == other.example && _table == other._table; @override int get hashCode => example.hashCode ^ _table.hashCode; @@ -33,8 +32,7 @@ class BddTableRows { /// ctx.table('notifications').row(0).val('read') as bool; BddTableValues row(int index) { if (index < 0 || index >= _values.length) - throw AssertionError( - "You can't get table row($index), since range is 0..${_values.length}."); + throw AssertionError("You can't get table row($index), since range is 0..${_values.length}."); else return _values[index]; } @@ -46,8 +44,7 @@ class BddTableRows { return _values.firstWhere( (BddTableValues btv) => btv.val(name) == value, orElse: () { - throw AssertionError( - 'There is no table with name:"$name" and value: "$name".'); + throw AssertionError('There is no table with name:"$name" and value: "$name".'); }, ); } @@ -61,10 +58,7 @@ class BddTableRows { @override bool operator ==(Object other) => - identical(this, other) || - (other is BddTableRows) && - (runtimeType == other.runtimeType) && - listEquals(_values, other._values); + identical(this, other) || (other is BddTableRows) && (runtimeType == other.runtimeType) && const ListEquality().equals(_values, other._values); @override int get hashCode => _values.hashCode; @@ -78,8 +72,7 @@ class BddMultipleTableValues { factory BddMultipleTableValues.from(List tableTerms) { Map> _tables = {}; for (BddTableTerm _table in tableTerms) { - List tableValues = - _table.rows.map((r) => BddTableValues.from(r.values)).toList(); + List tableValues = _table.rows.map((r) => BddTableValues.from(r.values)).toList(); _tables[_table.tableName] = tableValues; } @@ -88,8 +81,7 @@ class BddMultipleTableValues { List row(String tableName) { var table = _tables[tableName]; - if (table == null) - throw AssertionError('There is no table named "$tableName".'); + if (table == null) throw AssertionError('There is no table named "$tableName".'); return table; } @@ -98,10 +90,7 @@ class BddMultipleTableValues { @override bool operator ==(Object other) => - identical(this, other) || - (other is BddMultipleTableValues) && - (runtimeType == other.runtimeType) && - mapEquals(_tables, other._tables); + identical(this, other) || (other is BddMultipleTableValues) && (runtimeType == other.runtimeType) && const MapEquality().equals(_tables, other._tables); @override int get hashCode => _tables.hashCode; @@ -130,10 +119,7 @@ class BddTableValues { @override bool operator ==(Object other) => - identical(this, other) || - (other is BddTableValues) && - (runtimeType == other.runtimeType) && - mapEquals(_map, other._map); + identical(this, other) || (other is BddTableValues) && (runtimeType == other.runtimeType) && const MapEquality().equals(_map, other._map); @override int get hashCode => _map.hashCode; diff --git a/lib/src/core/bdd_engine.dart b/lib/src/core/bdd_engine.dart new file mode 100644 index 0000000..f55ac8c --- /dev/null +++ b/lib/src/core/bdd_engine.dart @@ -0,0 +1,249 @@ +import 'dart:async'; +import 'package:meta/meta.dart'; + +import 'bdd_base.dart'; +import 'bdd_context.dart'; + +/// A function type that represents the signature of a test runner (like `test` or `testWidgets`). +/// +/// This allows the [BddEngine] to be decoupled from the specific testing framework, +/// as the actual testing logic is injected via this function. +typedef BddTestFunction = void Function( + String description, + Future Function() body, { + dynamic timeout, + bool? skip, + dynamic tags, + Map? onPlatform, + int? retry, + dynamic testOn, +}); + +/// The core engine of the BDD framework. +/// +/// It is responsible for orchestrating the execution of BDD tests. It collects the +/// code blocks associated with each step, manages the test reporters, and handles +/// the execution flow for scenarios, including those with multiple examples. +class BddEngine { + /// Runs a BDD test by gathering its steps and executing them via the provided [testFn]. + /// + /// * [bdd]: The [BddFramework] instance containing the test structure. + /// * [code]: The [CodeRun] closure to be added as the final execution step. + /// * [testFn]: The underlying test runner function (from `package:test` or `package:flutter_test`). + /// * [errorHandler]: An optional callback to handle errors occurring during test execution. + void run( + BddFramework bdd, + CodeRun code, + BddTestFunction testFn, + void Function(Object error, StackTrace stackTrace)? errorHandler, + ) { + // Add the final implementation code to the BDD framework. + bdd.addCode(code); + + // Register this BDD test with all active reporters. + for (var reporter in BddReporter.reporters) { + reporter.addBdd(bdd); + } + + int numberOfExamples = bdd.numberOfExamples(); + BddReporter.runInfo.testCount++; + + // If there are no examples, run the scenario once. + if (numberOfExamples == 0) { + _runTheTest(bdd, null, testFn, errorHandler); + } + // Otherwise, run the scenario for each row in the Examples table. + else { + for (int i = 0; i < numberOfExamples; i++) { + _runTheTest(bdd, i, testFn, errorHandler); + } + } + } + + /// Default configuration used for the ANSI-colored console output during execution. + static const BddConfig config = BddConfig( + keywords: BddKeywords( + feature: '${boldItalic}Feature:$boldItalicOff', + scenario: '${boldItalic}Scenario:$boldItalicOff', + scenarioOutline: '${boldItalic}Scenario Outline:$boldItalicOff', + given: '${boldItalic}Given$boldItalicOff', + when: '${boldItalic}When$boldItalicOff', + then: '${boldItalic}Then$boldItalicOff', + and: '${boldItalic}And$boldItalicOff', + but: '${boldItalic}But$boldItalicOff', + comment: '$boldItalic#$boldItalicOff', + examples: '${boldItalic}Examples:$boldItalicOff', + ), + keywordPrefix: BddKeywords.only( + scenario: '\n', + scenarioOutline: '\n', + given: '\n', + when: '\n', + then: '\n', + examples: '\n', + comment: grey, + ), + suffix: BddKeywords.only( + comment: blue, + ), + ); + + /// Converts an integer into a subscript string (e.g., 1 -> ₁). + /// Used for numbering multiple example runs. + String subscript(int index) { + String result = ''; + var x = index.toString(); + for (int i = 0; i < x.length; i++) { + var char = x[i]; + result += {'0': '₀', '1': '₁', '2': '₂', '3': '₃', '4': '₄', '5': '₅', '6': '₆', '7': '₇', '8': '₈', '9': '₉'}[char]!; + } + return result; + } + + /// Generates a string representing the test iteration, potentially with a subscript for examples. + /// For example: "4" (no examples) or "4₁" (first example of test 4). + String testCountStr(int testCount, int? exampleNumber) => "$testCount${exampleNumber == null ? '' : subscript(exampleNumber + 1)}"; + + /// Internal method to execute the BDD steps within the provided [testFn] wrapper. + /// Handles retry logic, error reporting, and context initialization. + void _runTheTest( + BddFramework bdd, + int? exampleNumber, + BddTestFunction testFn, + void Function(Object error, StackTrace stackTrace)? errorHandler, + ) { + BddReporter.runInfo.totalTestCount++; + + var totalRetries = bdd.internalConfig?.retry ?? 0; + var currentExecution = 0; + + String bddStr = bdd.toString(config: config, withFeature: true); + int testCount = BddReporter.runInfo.testCount; + + if (bdd.internalSkip) BddReporter.runInfo.skipCount++; + + String _testCountStr = testCountStr(testCount, exampleNumber); + + testFn( + '$_testCountStr ${bdd.description()}', + () async { + currentExecution++; + + // Log the test start to console with ANSI colors. + print((currentExecution == 1) ? "${_header(bdd.internalSkip, _testCountStr)}$blue$bddStr$boldOff" : "\n${red}Retry $currentExecution.\n$boldOff"); + + // Initialize the BDD context with current example values and tables. + final example = BddTableValues.from(bdd.exampleRow(exampleNumber)); + final tables = BddMultipleTableValues.from(bdd.tables()); + final ctx = BddContext(example, tables); + + try { + // Sequentially execute all code blocks registered for this BDD scenario. + for (CodeRun codeRun in bdd.codeTerms.map((BddCodeTerm codeTerm) => codeTerm.codeRun)) { + await codeRun.call(ctx); + } + } catch (error, stacktrace) { + // Mark as failed and report. + bdd.passed.add(false); + BddReporter.runInfo.failedCount++; + print("\n"); + + if (errorHandler != null) { + errorHandler(error, stacktrace); + } else { + print("Exception: $error\n$stacktrace"); + } + + print(_fail(_testCountStr)); + return; + } + + // Mark as passed. + bdd.passed.add(true); + BddReporter.runInfo.passedCount++; + print(_footer(_testCountStr)); + }, + timeout: bdd.internalTimeout, + skip: bdd.internalSkip, + tags: bdd.internalConfig?.tags, + onPlatform: bdd.internalConfig?.onPlatform, + retry: totalRetries, + testOn: bdd.internalConfig?.testOn, + ); + } + + // ANSI color constants and helpers for console formatting. + static const red = "\x1B[38;5;9m"; + static const blue = "\x1B[38;5;45m"; + static const yellow = "\x1B[38;5;226m"; + static const grey = "\x1B[38;5;246m"; + static const bold = "\u001b[1m"; + static const italic = "\u001b[3m"; + static const boldItalic = bold + italic; + static const boldItalicOff = boldOff + italicOff; + static const boldOff = "\u001b[22m"; + static const italicOff = "\u001b[23m"; + static const reset = "\u001b[0m"; + + String _header(bool skip, String testNumberStr) { + return yellow + + italic + + "TEST $testNumberStr ${skip ? "SKIPPED" : ""} " + "$italicOff══════════════════════════════════════════════════$reset\n\n"; + } + + String _footer(String testNumberStr) => grey + "\n✔ ${italic}TEST $testNumberStr PASSED!\n\n" + italicOff; + + String _fail(String testNumberStr) => grey + "\n⚠ ${italic}TEST $testNumberStr FAILED!\n" + italicOff; +} + +/// A specialized engine instance used only for unit testing the BDD framework itself. +/// +/// It executes the steps immediately and synchronously (where possible) without +/// the formal [BddTestFunction] wrapper, allowing for direct assertions in framework tests. +class TestBddEngine { + /// The [CodeRun] block to execute. + final CodeRun code; + + /// An optional reporter to capture the results. + final BddReporter? reporter; + + @visibleForTesting + TestBddEngine(this.code, this.reporter); + + /// Executes the BDD test immediately. + void run(BddFramework bdd) { + // ignore: invalid_use_of_visible_for_testing_member + bdd.testAddThenCode(code); + reporter?.addBdd(bdd); + + int numberOfExamples = bdd.numberOfExamples(); + + if (numberOfExamples == 0) { + _runTheTest(bdd, null); + } else { + for (int i = 0; i < numberOfExamples; i++) { + _runTheTest(bdd, i); + } + } + } + + /// Internal synchronous/direct execution logic for framework tests. + void _runTheTest(BddFramework bdd, int? exampleNumber) { + final example = BddTableValues.from(bdd.exampleRow(exampleNumber)); + final tables = BddMultipleTableValues.from(bdd.tables()); + final ctx = BddContext(example, tables); + + if (!bdd.internalSkip) { + try { + Iterable codeRuns = bdd.codeTerms.map((BddCodeTerm codeTerm) => codeTerm.codeRun); + for (CodeRun codeRun in codeRuns) { + codeRun.call(ctx); + } + bdd.passed.add(true); + } catch (error) { + bdd.passed.add(false); + } + } + } +} diff --git a/lib/src/reporter.dart b/lib/src/reporter.dart new file mode 100644 index 0000000..67a63dd --- /dev/null +++ b/lib/src/reporter.dart @@ -0,0 +1,3 @@ +export 'reporters/console_reporter.dart'; +export 'reporters/feature_file_reporter.dart'; +export 'reporters/html_reporter.dart'; diff --git a/lib/src/reporters/console_reporter.dart b/lib/src/reporters/console_reporter.dart index cf48275..edb0e8f 100644 --- a/lib/src/reporters/console_reporter.dart +++ b/lib/src/reporters/console_reporter.dart @@ -1,6 +1,6 @@ import 'dart:io'; -import '../bdd_base.dart'; +import '../core.dart'; class ConsoleReporter extends BddReporter { static const config = BddConfig(rightAlignKeywords: true); diff --git a/lib/src/reporters/feature_file_reporter.dart b/lib/src/reporters/feature_file_reporter.dart index d704c94..f22394a 100644 --- a/lib/src/reporters/feature_file_reporter.dart +++ b/lib/src/reporters/feature_file_reporter.dart @@ -1,6 +1,6 @@ import 'dart:io'; -import '../bdd_base.dart'; +import '../core.dart'; class FeatureFileReporter extends BddReporter { // @@ -19,8 +19,7 @@ class FeatureFileReporter extends BddReporter { } /// Add a bar to the end of dir, only if necessary. - String get directory => - (dir.endsWith("/") || dir.endsWith("\\")) ? dir : dir + "/"; + String get directory => (dir.endsWith("/") || dir.endsWith("\\")) ? dir : dir + "/"; Future _init() async { // @@ -42,8 +41,7 @@ class FeatureFileReporter extends BddReporter { try { final fileName = normalizeFileName(feature.title); - final file = - await File('$directory$fileName.feature').create(recursive: true); + final file = await File('$directory$fileName.feature').create(recursive: true); stdout.write("Generating $file. "); sink = file.openWrite(); diff --git a/lib/src/reporters/html_reporter.dart b/lib/src/reporters/html_reporter.dart index 5df0b9f..173f2ec 100644 --- a/lib/src/reporters/html_reporter.dart +++ b/lib/src/reporters/html_reporter.dart @@ -1,4 +1,4 @@ -// import '../bdd_base.dart'; +// import '../core.dart'; // // class HtmlReporter extends BddReporter { // @override diff --git a/lib/src/runners/dart_test_runner.dart b/lib/src/runners/dart_test_runner.dart new file mode 100644 index 0000000..a95ed89 --- /dev/null +++ b/lib/src/runners/dart_test_runner.dart @@ -0,0 +1,62 @@ +import 'dart:async'; + +import 'package:test/test.dart'; + +import '../core.dart'; + +/// Extension providing the standard `.run()` method for pure Dart BDD tests. +/// +/// Use this entry point when you are writing tests for logic that does not depend on Flutter. +/// It wraps the execution within the standard `package:test` library's `test()` function. +extension BddDartTestRunner on BddRunnable { + /// Executes the BDD scenario as a standard Dart unit test. + /// + /// This method uses the [BddEngine] to orchestrate step execution and delegates the + /// underlying test lifecycle management to `package:test`. + void run([FutureOr Function(BddContext ctx)? testRun]) { + BddEngine().run( + bdd, + testRun ?? (ctx) async {}, // Default empty CodeRun + _dartTestRunner, + null, // No custom error handler for pure Dart, let it bubble up to `test` framework + ); + } +} + +/// Extension to attach Dart-specific executable closures (code blocks) to BDD terms. +/// +/// By importing `bdd_dart.dart`, these extensions become available on steps like +/// [BddGiven], [BddWhen], and [BddThen], allowing you to define the logic for each step. +extension BddDartTestCodeable> on BddCodeable { + /// Attaches a code block that executes within a pure Dart [BddContext]. + /// + /// The [codeRun] function receives a [BddContext] which provides access to + /// values defined in Example tables. + T code(FutureOr Function(BddContext ctx) codeRun) { + addCode(codeRun); + return this as T; + } +} + +/// Internal helper that bridges the [BddEngine] to the pure Dart `test()` function. +void _dartTestRunner( + String description, + Future Function() body, { + dynamic timeout, + bool? skip, + dynamic tags, + Map? onPlatform, + int? retry, + dynamic testOn, +}) { + test( + description, + body, + timeout: timeout != null ? Timeout(timeout) : null, + skip: skip, + tags: tags, + onPlatform: onPlatform, + retry: retry, + testOn: testOn, + ); +} diff --git a/lib/src/runners/flutter_test_runner.dart b/lib/src/runners/flutter_test_runner.dart new file mode 100644 index 0000000..02e4d2b --- /dev/null +++ b/lib/src/runners/flutter_test_runner.dart @@ -0,0 +1,109 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../core.dart'; + +/// Extension to enable running Flutter non-UI tests directly via `test`. +/// +/// Use this entry point when you are writing Flutter tests that only involve logic +/// or pure Dart code, but still need to run within the Flutter test environment. +extension BddFlutterTestRunner on BddRunnable { + /// Executes the BDD scenario as a Flutter unit test (without a widget tester). + /// + /// This method natively uses the standard `test` function from `package:flutter_test`. + void run([FutureOr Function(BddContext ctx)? testRun]) { + BddEngine().run( + bdd, + testRun ?? (ctx) async {}, + _flutterTestRunner, + _flutterErrorHandler, + ); + } +} + +/// Extension to attach pure Flutter non-UI executable closures (code blocks) to BDD terms. +/// +/// By importing `bdd_flutter_test.dart`, these extensions become available. +extension BddFlutterTestCodeable> on BddCodeable { + /// Attaches a code block that executes within a Flutter [BddContext]. + /// + /// This version of `.code()` does not provide a `WidgetTester`. Use it for logic-only steps. + T code(FutureOr Function(BddContext ctx) codeRun) { + addCode(codeRun); + return this as T; + } +} + +void _flutterTestRunner( + String description, + Future Function() body, { + dynamic timeout, + bool? skip, + dynamic tags, + Map? onPlatform, + int? retry, + dynamic testOn, +}) { + test( + description, + () async { + if (BddFlutter.ignoreOverflow) _ignoreOverflowErrors(); + try { + await body(); + } finally { + _cleanTargetPlatformOverride(); + } + }, + skip: skip, + timeout: timeout != null ? Timeout(timeout) : null, + tags: tags, + retry: retry, + ); +} + +void _flutterErrorHandler(Object error, StackTrace stackTrace) { + var errorDetails = FlutterErrorDetails( + library: 'BDD Framework', + exception: error, + stack: stackTrace, + stackFilter: _stackFilter, + ); + reportTestException(errorDetails, ""); +} + +Iterable _stackFilter(Iterable frames) { + // Removes the frames we are not interested in. + var filteredFrames = frames.where((frame) => + !frame.contains("package:matcher/") && + !frame.contains("package:flutter_test/src/widget_tester.dart") && + !frame.contains("package:bdd_framework/src/") && + !frame.contains("package:test_api/src/")); + + return FlutterError.defaultStackFilter(filteredFrames); +} + +void _ignoreOverflowErrors() { + var handlerOriginal = FlutterError.onError; + FlutterError.onError = (details) { + var exception = details.exception; + var ifOverflow = (exception is FlutterError) && + exception.diagnostics + .map((diagnostic) => diagnostic.value) + .whereType>() + .expand((value) => value) + .any((data) => data.toString().startsWith("A RenderFlex overflowed by")); + + if (ifOverflow) + FlutterError.dumpErrorToConsole(details); + else + handlerOriginal!(details); + }; +} + +void _cleanTargetPlatformOverride() => (debugDefaultTargetPlatformOverride = null); + +class BddFlutter { + static bool ignoreOverflow = true; +} diff --git a/lib/src/runners/flutter_widget_test_runner.dart b/lib/src/runners/flutter_widget_test_runner.dart new file mode 100644 index 0000000..c8de3ca --- /dev/null +++ b/lib/src/runners/flutter_widget_test_runner.dart @@ -0,0 +1,135 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../core.dart'; + +/// Extension to enable running Flutter UI tests directly via `testWidgets`. +/// +/// Use this entry point when you are writing Flutter Widget tests that require +/// a [WidgetTester] to interact with the UI. +extension BddFlutterWidgetTestRunner on BddRunnable { + /// Executes the BDD scenario as a Flutter widget test. + /// + /// This method natively uses `testWidgets` from `package:flutter_test`. + void run([FutureOr Function(BddContext ctx, WidgetTester tester)? testRun]) { + BddEngine().run( + bdd, + (ctx) async { + if (testRun != null) { + final tester = Zone.current[#tester] as WidgetTester?; + if (tester == null) { + throw StateError('WidgetTester not found in context. ' + 'Did you forget to use ".run()" imported from `bdd_flutter_widget_test.dart` at the end of the scenario?'); + } + await testRun(ctx, tester); + } + }, + _flutterTestWidgetsRunner, + _flutterErrorHandler, + ); + } +} + +/// Extension to attach Flutter-specific executable closures (code blocks) to BDD terms. +/// +/// By importing `bdd_flutter_widget_test.dart`, these extensions become available, +/// allowing you to access the [WidgetTester] in every step. +extension BddFlutterWidgetTestCodeable> on BddCodeable { + /// Attaches a code block that injects the [WidgetTester] directly into the callback. + /// + /// The [codeRun] function receives both the [BddContext] and the [WidgetTester] + /// currently active for the test. + T code(FutureOr Function(BddContext ctx, WidgetTester tester) codeRun) { + addCode((ctx) async { + final tester = Zone.current[#tester] as WidgetTester?; + if (tester == null) { + throw StateError('WidgetTester not found in context. ' + 'Did you forget to use ".run()" imported from `bdd_flutter_widget_test.dart` at the end of the scenario?'); + } + await codeRun(ctx, tester); + }); + return this as T; + } +} + +/// A runner that injects the tester into the context. +void _flutterTestWidgetsRunner( + String description, + Future Function() body, { + dynamic timeout, + bool? skip, + dynamic tags, + Map? onPlatform, + int? retry, + dynamic testOn, +}) { + testWidgets( + description, + (tester) async { + if (BddFlutter.ignoreOverflow) _ignoreOverflowErrors(); + try { + // To pass the tester down to the `CodeRun` context during the execution phase, + // we can use a Zone. + await runZoned( + () async { + await body(); + }, + zoneValues: {#tester: tester}, + ); + } finally { + _cleanTargetPlatformOverride(); + } + }, + skip: skip, + timeout: timeout != null ? Timeout(timeout) : null, + tags: tags, + retry: retry, + ); +} + +void _flutterErrorHandler(Object error, StackTrace stackTrace) { + var errorDetails = FlutterErrorDetails( + library: 'BDD Framework', + exception: error, + stack: stackTrace, + stackFilter: _stackFilter, + ); + reportTestException(errorDetails, ""); +} + +Iterable _stackFilter(Iterable frames) { + // Removes the frames we are not interested in. + var filteredFrames = frames.where((frame) => + !frame.contains("package:matcher/") && + !frame.contains("package:flutter_test/src/widget_tester.dart") && + !frame.contains("package:bdd_framework/src/") && + !frame.contains("package:test_api/src/")); + + return FlutterError.defaultStackFilter(filteredFrames); +} + +void _ignoreOverflowErrors() { + var handlerOriginal = FlutterError.onError; + FlutterError.onError = (details) { + var exception = details.exception; + var ifOverflow = (exception is FlutterError) && + exception.diagnostics + .map((diagnostic) => diagnostic.value) + .whereType>() + .expand((value) => value) + .any((data) => data.toString().startsWith("A RenderFlex overflowed by")); + + if (ifOverflow) + FlutterError.dumpErrorToConsole(details); + else + handlerOriginal!(details); + }; +} + +void _cleanTargetPlatformOverride() => (debugDefaultTargetPlatformOverride = null); + +class BddFlutter { + static bool ignoreOverflow = true; +} diff --git a/lib/src/runners/patrol_test_runner.dart b/lib/src/runners/patrol_test_runner.dart new file mode 100644 index 0000000..b03f20c --- /dev/null +++ b/lib/src/runners/patrol_test_runner.dart @@ -0,0 +1,137 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:patrol/patrol.dart'; + +import '../core.dart'; + +/// Extension to enable running Flutter UI tests via `patrolTest`. +/// +/// Use this entry point when you are writing integration tests that require +/// Patrol's native automation and enhanced finders. +extension BddPatrolTestRunner on BddRunnable { + /// Executes the BDD scenario as a Patrol integration test. + /// + /// This method injects the [PatrolIntegrationTester] directly into the `BddContext` + /// and wraps the execution with [patrolTest]. + void run([FutureOr Function(BddContext ctx, PatrolIntegrationTester $)? testRun]) { + BddEngine().run( + bdd, + (ctx) async { + if (testRun != null) { + final $ = Zone.current[#patrolTester] as PatrolIntegrationTester?; + if ($ == null) { + throw StateError('PatrolIntegrationTester not found in context. ' + 'Did you forget to use ".run()" imported from `package:bdd_framework/patrol_test.dart` at the end of the scenario?'); + } + await testRun(ctx, $); + } + }, + _patrolRunner, + _patrolErrorHandler, + ); + } +} + +/// A runner that injects the [PatrolIntegrationTester] into the context. +void _patrolRunner( + String description, + Future Function() body, { + dynamic timeout, + bool? skip, + dynamic tags, + Map? onPlatform, + int? retry, + dynamic testOn, +}) { + patrolTest( + description, + ($) async { + if (BddPatrol.ignoreOverflow) _ignoreOverflowErrors(); + try { + await runZoned( + () async { + await body(); + }, + zoneValues: {#patrolTester: $}, + ); + } finally { + _cleanTargetPlatformOverride(); + } + }, + skip: skip, + timeout: timeout != null ? Timeout(timeout) : null, + tags: tags, + ); +} + +/// Extension to attach Patrol-specific executable closures (code blocks) to BDD terms. +/// +/// By importing `patrol_test.dart`, these extensions become available, +/// allowing you to access the [PatrolIntegrationTester] (often named `$`) in every step. +extension BddPatrolTestCodeable> on BddCodeable { + /// Attaches a code block that injects the [PatrolIntegrationTester] directly into the callback. + /// + /// The [codeRun] function receives both the [BddContext] and the [PatrolIntegrationTester] + /// currently active for the test. + T code(FutureOr Function(BddContext ctx, PatrolIntegrationTester $) codeRun) { + addCode((ctx) async { + final $ = Zone.current[#patrolTester] as PatrolIntegrationTester?; + if ($ == null) { + throw StateError('PatrolIntegrationTester not found in context. ' + 'Did you forget to use ".run()" imported from `package:bdd_framework/patrol_test.dart` at the end of the scenario?'); + } + await codeRun(ctx, $); + }); + return this as T; + } +} + +void _patrolErrorHandler(Object error, StackTrace stackTrace) { + var errorDetails = FlutterErrorDetails( + library: 'BDD Framework', + exception: error, + stack: stackTrace, + stackFilter: _stackFilter, + ); + reportTestException(errorDetails, ""); +} + +Iterable _stackFilter(Iterable frames) { + // Removes the frames we are not interested in. + var filteredFrames = frames.where((frame) => + !frame.contains("package:matcher/") && + !frame.contains("package:flutter_test/") && + !frame.contains("package:patrol/") && + !frame.contains("package:bdd_framework/src/") && + !frame.contains("package:test_api/src/")); + + return FlutterError.defaultStackFilter(filteredFrames); +} + +void _ignoreOverflowErrors() { + var handlerOriginal = FlutterError.onError; + FlutterError.onError = (details) { + var exception = details.exception; + var ifOverflow = (exception is FlutterError) && + exception.diagnostics + .map((diagnostic) => diagnostic.value) + .whereType>() + .expand((value) => value) + .any((data) => data.toString().startsWith("A RenderFlex overflowed by")); + + if (ifOverflow) + FlutterError.dumpErrorToConsole(details); + else + handlerOriginal!(details); + }; +} + +void _cleanTargetPlatformOverride() => (debugDefaultTargetPlatformOverride = null); + +/// Configuration for Patrol-specific BDD settings. +class BddPatrol { + /// Whether to suppress RenderFlex overflow errors in logs. + static bool ignoreOverflow = true; +} diff --git a/patrol_test/patrol_test_runner_test.dart b/patrol_test/patrol_test_runner_test.dart new file mode 100644 index 0000000..1a00610 --- /dev/null +++ b/patrol_test/patrol_test_runner_test.dart @@ -0,0 +1,22 @@ +import 'package:bdd_framework/patrol_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + Bdd(BddFeature('Patrol Test Runner Testing')) + .scenario('Validating that run works correctly and injects the tester.') + .given('A standard patrol setup') + .when('The run is invoked') + .then('It correctly provides a PatrolIntegrationTester and delegates execution') + .code((ctx, $) async { + // Verify tester ($) is available and functions. + await $.tester.pumpWidget(const MaterialApp( + home: Scaffold( + body: Text('Hello BDD with Patrol'), + ), + )); + + // Use Patrol finder shortcut + expect($('Hello BDD with Patrol'), findsOneWidget); + }).run(); +} diff --git a/pubspec.lock b/pubspec.lock index dc6f6fc..7bfb64e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,30 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d + url: "https://pub.dev" + source: hosted + version: "91.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08 + url: "https://pub.dev" + source: hosted + version: "8.4.1" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: @@ -25,6 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" clock: dependency: transitive description: @@ -41,6 +73,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" + url: "https://pub.dev" + source: hosted + version: "1.15.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + dispose_scope: + dependency: transitive + description: + name: dispose_scope + sha256: "48ec38ca2631c53c4f8fa96b294c801e55c335db5e3fb9f82cede150cfe5a2af" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + equatable: + dependency: transitive + description: + name: equatable + sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b" + url: "https://pub.dev" + source: hosted + version: "2.0.8" fake_async: dependency: transitive description: @@ -49,6 +121,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" flutter: dependency: "direct main" description: flutter @@ -67,6 +147,70 @@ packages: description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 + url: "https://pub.dev" + source: hosted + version: "4.11.0" leak_tracker: dependency: transitive description: @@ -99,6 +243,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: @@ -119,10 +271,34 @@ packages: dependency: "direct main" description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "2.2.0" path: dependency: transitive description: @@ -131,11 +307,99 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + patrol: + dependency: "direct main" + description: + name: patrol + sha256: "0688a00e0fda2e42f102863bbac969b7b5ea836d4dc365b750e0e5aed59d34b0" + url: "https://pub.dev" + source: hosted + version: "4.1.1" + patrol_finders: + dependency: transitive + description: + name: patrol_finders + sha256: ac0bfaf3eaaa6cc3d49c8a365329cc7f4361a5f486f1adb45edc96dbfc854da9 + url: "https://pub.dev" + source: hosted + version: "3.1.0" + patrol_log: + dependency: transitive + description: + name: patrol_log + sha256: "26af8e1a8bbea313c82664d4eff1cace4fc8acbc2893799dd67fe4c5d3fa47df" + url: "https://pub.dev" + source: hosted + version: "0.7.0" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.0" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + url: "https://pub.dev" + source: hosted + version: "0.10.13" source_span: dependency: transitive description: @@ -176,14 +440,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.2" + test: + dependency: "direct main" + description: + name: test + sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" + url: "https://pub.dev" + source: hosted + version: "1.26.3" test_api: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + url: "https://pub.dev" + source: hosted + version: "0.7.7" + test_core: + dependency: transitive + description: + name: test_core + sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.6.12" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" vector_math: dependency: transitive description: @@ -200,6 +488,54 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.2" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" sdks: - dart: ">=3.8.0-0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.32.0" diff --git a/pubspec.yaml b/pubspec.yaml index 74d6cfa..b4f776e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,5 +23,7 @@ dependencies: flutter_test: sdk: flutter + test: ^1.24.0 + patrol: ^4.1.1 dev_dependencies: flutter_lints: ^3.0.1 diff --git a/test/src/runners/dart_test_runner_test.dart b/test/src/runners/dart_test_runner_test.dart new file mode 100644 index 0000000..5158de2 --- /dev/null +++ b/test/src/runners/dart_test_runner_test.dart @@ -0,0 +1,13 @@ +import 'package:bdd_framework/dart_test.dart'; +import 'package:test/test.dart'; + +void main() { + Bdd(BddFeature('Dart Test Runner Testing')) + .scenario('Validating that run works correctly in pure Dart environment.') + .given('A standard setup') + .when('The dart run is invoked') + .then('It correctly delegates to the underlying package:test implementation') + .code((ctx) { + expect(true, isTrue); + }).run(); +} diff --git a/test/src/runners/flutter_test_runner_test.dart b/test/src/runners/flutter_test_runner_test.dart new file mode 100644 index 0000000..1cbeab7 --- /dev/null +++ b/test/src/runners/flutter_test_runner_test.dart @@ -0,0 +1,13 @@ +import 'package:bdd_framework/flutter_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + Bdd(BddFeature('Flutter Test Runner Testing')) + .scenario('Validating that run works correctly in Flutter logic environment.') + .given('A standard flutter setup without widget interactions') + .when('The run is invoked') + .then('It correctly delegates execution without providing a WidgetTester') + .code((ctx) { + expect(true, isTrue); + }).run(); +} diff --git a/test/src/runners/flutter_widget_test_runner_test.dart b/test/src/runners/flutter_widget_test_runner_test.dart new file mode 100644 index 0000000..4096f7a --- /dev/null +++ b/test/src/runners/flutter_widget_test_runner_test.dart @@ -0,0 +1,20 @@ +import 'package:bdd_framework/flutter_widget_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + Bdd(BddFeature('Flutter Widget Test Runner Testing')) + .scenario('Validating that run works correctly in Flutter environment.') + .given('A standard flutter setup') + .when('The run is invoked') + .then('It correctly provides a WidgetTester and delegates execution') + .code((ctx, WidgetTester tester) async { + // Verify tester is available and functions. + await tester.pumpWidget(const Directionality( + textDirection: TextDirection.ltr, + child: Text('Hello BDD'), + )); + + expect(find.text('Hello BDD'), findsOneWidget); + }).run(); +}