- Fix: #165: Empty .feature files causing void functions which get compiled out at runtime and cause errors

- Fix: #162: Incorrect feature name in HTML reports - many thanks to @AFASbart for suggesting the cause and fix.
- Fix: #159: Swipe step is not working due to bad '??' statement
- Fix: #155: Ensure stdout reporter only add ascii colour code when the target supports it
This commit is contained in:
Jon Samwell 2021-11-10 11:14:17 +11:00
parent 2db755d23b
commit 29eeb73f39
14 changed files with 348 additions and 196 deletions

View File

@ -1 +1 @@
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"integration_test","path":"C:\\\\Google\\\\flutter\\\\packages\\\\integration_test\\\\","dependencies":[]}],"android":[{"name":"integration_test","path":"C:\\\\Google\\\\flutter\\\\packages\\\\integration_test\\\\","dependencies":[]}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"integration_test","dependencies":[]}],"date_created":"2021-10-27 20:12:58.071212","version":"2.5.3"}
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"integration_test","path":"C:\\\\Google\\\\flutter\\\\packages\\\\integration_test\\\\","dependencies":[]}],"android":[{"name":"integration_test","path":"C:\\\\Google\\\\flutter\\\\packages\\\\integration_test\\\\","dependencies":[]}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"integration_test","dependencies":[]}],"date_created":"2021-11-10 11:13:27.428521","version":"2.5.3"}

View File

@ -1,3 +1,10 @@
## [3.0.0-rc.7] - 10/11/2021
- Fix: #165: Empty .feature files causing void functions which get compiled out at runtime and cause errors
- Fix: #162: Incorrect feature name in HTML reports - many thanks to @AFASbart for suggesting the cause and fix.
- Fix: #159: Swipe step is not working due to bad '??' statement
- Fix: #155: Ensure stdout reporter only add ascii colour code when the target supports it
## [3.0.0-rc.6] - 27/10/2021
- BREAKING CHANGE: Made `appMainFunction` return a `Future<void>` so it can be async

View File

@ -1,6 +1,6 @@
```
# generate the test suite
flutter pub run build_runner build
flutter pub run build_runner build --delete-conflicting-outputs
# run the tests
flutter drive --driver=test_driver/integration_test_driver.dart --target=integration_test/gherkin_suite_test.dart

View File

@ -23,32 +23,32 @@ Feature: Creating todos
| Buy apples |
| Buy carrots |
Given I wait 5 seconds for the animation to complete
# Given I have item with data
# """
# {
# "glossary": {
# "title": "example glossary",
# "GlossDiv": {
# "title": "S",
# "GlossList": {
# "GlossEntry": {
# "ID": "SGML",
# "SortAs": "SGML",
# "GlossTerm": "Standard Generalized Markup Language",
# "Acronym": "SGML",
# "Abbrev": "ISO 8879:1986",
# "GlossDef": {
# "para": "A meta-markup language, used to create markup languages such as DocBook.",
# "GlossSeeAlso": [
# "GML",
# "XML"
# ]
# },
# "GlossSee": "markup"
# }
# }
# }
# }
# }
# """
# When I test the default step timeout is not applied to step with custom timeout
Given I have item with data
"""
{
"glossary": {
"title": "example glossary",
"GlossDiv": {
"title": "S",
"GlossList": {
"GlossEntry": {
"ID": "SGML",
"SortAs": "SGML",
"GlossTerm": "Standard Generalized Markup Language",
"Acronym": "SGML",
"Abbrev": "ISO 8879:1986",
"GlossDef": {
"para": "A meta-markup language, used to create markup languages such as DocBook.",
"GlossSeeAlso": [
"GML",
"XML"
]
},
"GlossSee": "markup"
}
}
}
}
}
"""
# When I test the default step timeout is not applied to step with custom timeout

View File

@ -0,0 +1,10 @@
@tag
Feature: Swiping
@debug
Scenario: User can swipe cards left and right
Given I swipe right by 250 pixels on the "scrollable cards"`
Then Then I expect the text "Page 2" to be present
Given I swipe left by 250 pixels on the "scrollable cards"`
Then Then I expect the text "Page 1" to be present

View File

@ -16,9 +16,113 @@ class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner {
void onRun() {
testFeature0();
testFeature1();
testFeature2();
}
void testFeature0() {
runFeature(
'Swiping:',
<String>['@tag'],
() {
runScenario(
'User can swipe cards left and right',
<String>['@tag', '@debug'],
(TestDependencies dependencies) async {
await runStep(
'Given I swipe right by 250 pixels on the "scrollable cards"`',
<String>[],
null,
dependencies,
);
await runStep(
'Then Then I expect the text "Page 2" to be present',
<String>[],
null,
dependencies,
);
await runStep(
'Given I swipe left by 250 pixels on the "scrollable cards"`',
<String>[],
null,
dependencies,
);
await runStep(
'Then Then I expect the text "Page 1" to be present',
<String>[],
null,
dependencies,
);
},
onBefore: () async => onBeforeRunFeature(
'Swiping',
<String>['@tag'],
),
onAfter: () async => onAfterRunFeature(
'Swiping',
),
);
},
);
}
void testFeature1() {
runFeature(
'Checking data:',
<String>['@tag'],
() {
runScenario(
'User can have data',
<String>['@tag', '@tag1'],
(TestDependencies dependencies) async {
await runStep(
'Given I have item with data',
<String>[
"""{
"glossary": {
"title": "example glossary",
"GlossDiv": {
"title": "S",
"GlossList": {
"GlossEntry": {
"ID": "SGML",
"SortAs": "SGML",
"GlossTerm": "Standard Generalized Markup Language",
"Acronym": "SGML",
"Abbrev": "ISO 8879:1986",
"GlossDef": {
"para": "A meta-markup language, used to create markup languages such as DocBook.",
"GlossSeeAlso": [
"GML",
"XML"
]
},
"GlossSee": "markup"
}
}
}
}
}"""
],
null,
dependencies,
);
},
onBefore: () async => onBeforeRunFeature(
'Checking data',
<String>['@tag'],
),
onAfter: () async => onAfterRunFeature(
'Checking data',
),
);
},
);
}
void testFeature2() {
runFeature(
'Creating todos:',
<String>['@tag'],
@ -49,8 +153,8 @@ class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner {
);
},
onBefore: () async => onBeforeRunFeature(
'User can create a new todo item',
<String>['@tag', '@tag1', '@tag_two'],
'Creating todos',
<String>['@tag'],
),
onAfter: null,
);
@ -115,25 +219,7 @@ class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner {
null,
dependencies,
);
},
onBefore: null,
onAfter: () async => onAfterRunFeature(
'User can create multiple new todo items',
),
);
},
);
}
void testFeature1() {
runFeature(
'Checking data:',
<String>['@tag'],
() {
runScenario(
'User can have data',
<String>['@tag', '@tag1'],
(TestDependencies dependencies) async {
await runStep(
'Given I have item with data',
<String>[
@ -167,12 +253,9 @@ class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner {
dependencies,
);
},
onBefore: () async => onBeforeRunFeature(
'User can have data',
<String>['@tag', '@tag1'],
),
onBefore: null,
onAfter: () async => onAfterRunFeature(
'User can have data',
'Creating todos',
),
);
},

View File

@ -26,22 +26,29 @@ class TodoApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final app = MaterialApp(
title: 'Todo App',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: injector.get<HomeView>(),
);
// if the app is in test mode and an ExternalApplicationManager is passed in
// let the app respond to external changes
return externalApplicationManager == null
? app
? MaterialApp(
title: 'Todo App',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: injector.get<HomeView>(),
)
: StreamBuilder(
stream: externalApplicationManager!.applicationReset,
builder: (ctx, _) => app,
builder: (ctx, _) {
return MaterialApp(
title: 'Todo App',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: injector.get<HomeView>(),
);
},
);
}
}

View File

@ -33,115 +33,147 @@ class _HomeViewState extends State<HomeView> with ViewUtilsMixin {
),
),
body: SafeArea(
child: Column(
children: [
SizedBox(
height: 100,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
),
child: AddTodoComponent(
todo: bloc.newModel,
onAdded: (todo) {
subscribeOnce(bloc.add(todo));
},
child: SingleChildScrollView(
child: Column(
children: [
SizedBox(
height: 100,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
),
child: AddTodoComponent(
todo: bloc.newModel,
onAdded: (todo) {
subscribeOnce(bloc.add(todo));
},
),
),
),
),
SizedBox(
height: 16,
),
StreamBuilder<Iterable<Todo>>(
stream: bloc.todos,
builder: (_, snapshot) {
if (snapshot.hasData) {
final data = snapshot.data!;
if (data.isEmpty) {
return Center(
child: Column(
children: [
Icon(
Icons.list,
size: 64,
color: Colors.black26,
),
Padding(
padding: const EdgeInsets.all(16),
child: Text(
'No todos!',
key: const Key('empty'),
style: Theme.of(context).textTheme.headline6,
SizedBox(
height: 16,
),
StreamBuilder<Iterable<Todo>>(
stream: bloc.todos,
builder: (_, snapshot) {
if (snapshot.hasData) {
final data = snapshot.data!;
if (data.isEmpty) {
return Center(
child: Column(
children: [
Icon(
Icons.list,
size: 64,
color: Colors.black26,
),
),
],
),
);
Padding(
padding: const EdgeInsets.all(16),
// child: Text(
// 'No todos!',
// key: const Key('empty'),
// style: Theme.of(context).textTheme.headline6,
// ),
child: SizedBox(
key: const Key('scrollable cards'),
width: 300,
height: 250,
child: PageView.builder(
itemCount: 3,
itemBuilder: (ctx, index) {
return Container(
margin: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: index == 0
? Colors.amber
: Colors.blueAccent,
borderRadius: BorderRadius.circular(10),
),
child: SizedBox(
key: Key('Page ${index + 1}'),
width: 200,
height: 200,
child: Center(
child: Text(
'Page ${index + 1}',
),
),
),
);
},
),
),
),
],
),
);
} else {
return ListView.builder(
shrinkWrap: true,
itemCount: data.length,
itemBuilder: (ctx, index) {
final todo = data.elementAt(index);
return Dismissible(
background: Container(
color: Colors.red,
padding: const EdgeInsets.symmetric(
horizontal: 8,
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Icon(
Icons.delete,
color: Colors.white,
),
Icon(
Icons.delete,
color: Colors.white,
),
],
),
),
key: Key(todo.action!),
onDismissed: (direction) {
subscribeOnce(
bloc.remove(todo),
onDone: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Todo deleted'),
),
);
},
);
},
child: ListTile(
title: Text(
todo.action!,
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(
decoration:
todo.status == TodoStatus.complete
? TextDecoration.lineThrough
: null,
),
),
),
);
},
);
}
} else {
return ListView.builder(
shrinkWrap: true,
itemCount: data.length,
itemBuilder: (ctx, index) {
final todo = data.elementAt(index);
return Dismissible(
background: Container(
color: Colors.red,
padding: const EdgeInsets.symmetric(
horizontal: 8,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(
Icons.delete,
color: Colors.white,
),
Icon(
Icons.delete,
color: Colors.white,
),
],
),
),
key: Key(todo.action!),
onDismissed: (direction) {
subscribeOnce(
bloc.remove(todo),
onDone: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Todo deleted'),
),
);
},
);
},
child: ListTile(
title: Text(
todo.action!,
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(
decoration:
todo.status == TodoStatus.complete
? TextDecoration.lineThrough
: null,
),
),
),
);
},
return Center(
child: Text('Loading...'),
);
}
} else {
return Center(
child: Text('Loading...'),
);
}
},
),
],
},
),
],
),
),
),
);

View File

@ -213,7 +213,7 @@ packages:
path: ".."
relative: true
source: path
version: "3.0.0-rc.6"
version: "3.0.0-rc.7"
flutter_simple_dependency_injection:
dependency: "direct main"
description:
@ -249,7 +249,7 @@ packages:
name: gherkin
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5+1"
version: "2.0.7"
glob:
dependency: transitive
description:

View File

@ -151,7 +151,7 @@ class WidgetTesterAppDriverAdapter
}) async {
final scrollableFinder = findByDescendant(
finder,
find.byType(Scrollable).first,
find.byType(Scrollable),
matchRoot: true,
);
final state = nativeDriver.state(scrollableFinder) as ScrollableState;

View File

@ -149,10 +149,10 @@ class FeatureFileTestGeneratorVisitor extends FeatureFileVisitor {
);
''';
static const String ON_BEFORE_SCENARIO_RUN = '''
() async => onBeforeRunFeature('{{scenario_name}}', {{tags}},)
() async => onBeforeRunFeature('{{feature_name}}', {{feature_tags}},)
''';
static const String ON_AFTER_SCENARIO_RUN = '''
() async => onAfterRunFeature('{{scenario_name}}',)
() async => onAfterRunFeature('{{feature_name}}',)
''';
final StringBuffer _buffer = StringBuffer();
@ -188,26 +188,31 @@ class FeatureFileTestGeneratorVisitor extends FeatureFileVisitor {
String name,
String? description,
Iterable<String> tags,
int childScenarioCount,
) async {
_currentFeatureCode = _replaceVariable(
FUNCTION_TEMPLATE,
'feature_number',
_id.toString(),
);
_currentFeatureCode = _replaceVariable(
_currentFeatureCode!,
'feature_name',
_escapeText(name),
);
_currentFeatureCode = _replaceVariable(
_currentFeatureCode!,
'tags',
'<String>[${tags.map((e) => "'$e'").join(', ')}]',
);
if (childScenarioCount > 0) {
_currentFeatureCode = _replaceVariable(
FUNCTION_TEMPLATE,
'feature_number',
_id.toString(),
);
_currentFeatureCode = _replaceVariable(
_currentFeatureCode!,
'feature_name',
_escapeText(name),
);
_currentFeatureCode = _replaceVariable(
_currentFeatureCode!,
'tags',
'<String>[${tags.map((e) => "'$e'").join(', ')}]',
);
}
}
@override
Future<void> visitScenario(
String featureName,
Iterable<String> featureTags,
String name,
Iterable<String> tags,
bool isFirst,
@ -224,6 +229,16 @@ class FeatureFileTestGeneratorVisitor extends FeatureFileVisitor {
'onAfter',
isLast ? ON_AFTER_SCENARIO_RUN : 'null',
);
_currentScenarioCode = _replaceVariable(
_currentScenarioCode!,
'feature_name',
_escapeText(featureName),
);
_currentScenarioCode = _replaceVariable(
_currentScenarioCode!,
'feature_tags',
'<String>[${featureTags.map((e) => "'$e'").join(', ')}]',
);
_currentScenarioCode = _replaceVariable(
_currentScenarioCode!,
'scenario_name',

View File

@ -18,7 +18,6 @@ mixin _SwipeHelper
await world.appDriver.scroll(
finder,
dx: offset.toDouble(),
dy: 0,
duration: Duration(milliseconds: 500),
timeout: timeout,
);
@ -28,7 +27,6 @@ mixin _SwipeHelper
await world.appDriver.scroll(
finder,
dx: 0,
dy: offset.toDouble(),
duration: Duration(milliseconds: 500),
timeout: timeout,
@ -58,7 +56,7 @@ class SwipeOnKeyStep
@override
RegExp get pattern =>
RegExp(r'I swipe {swipe_direction} by {int} pixels on the {string}$');
RegExp(r'I swipe {swipe_direction} by {int} pixels on the {string}');
}
/// Swipes in a cardinal direction on a widget discovered by its test.

View File

@ -160,7 +160,7 @@ packages:
name: gherkin
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5+1"
version: "2.0.7"
glob:
dependency: "direct main"
description:

View File

@ -1,6 +1,6 @@
name: flutter_gherkin
description: A Gherkin / Cucumber parser and test runner for Dart and Flutter
version: 3.0.0-rc.6
version: 3.0.0-rc.7
homepage: https://github.com/jonsamwell/flutter_gherkin
environment:
@ -18,7 +18,7 @@ dependencies:
sdk: flutter
analyzer: ">=1.7.1 <3.0.0"
collection: ^1.15.0
gherkin: ^2.0.5+1
gherkin: ^2.0.7
source_gen: ^1.1.1
build: ^2.1.1
glob: ^2.0.2