Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 54 additions & 5 deletions lib/src/scenario_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,59 @@ String _replacePlaceholders(
return headReplaced + tailReplaced;
}
}
// Default behaviour: placeholders become parameters
var replaced = line;
for (final e in example.keys) {
replaced = replaced.replaceAll('<$e>', '{${example[e]}}');

return _replacePlaceholdersWithContext(line, example);
}

// Placeholders inside {} blocks become raw values,
// Placeholders outside {} blocks become parameters (wrapped with {})
String _replacePlaceholdersWithContext(
String line,
Map<String, String> example,
) {
final result = StringBuffer();
var i = 0;
var braceDepth = 0;

while (i < line.length) {
// Track brace depth to know if we're inside a parameter block
if (line[i] == '{') {
braceDepth++;
result.write('{');
i++;
} else if (line[i] == '}') {
braceDepth--;
result.write('}');
i++;
} else if (line[i] == '<') {
// Check if this is a placeholder
var foundPlaceholder = false;
for (final key in example.keys) {
final placeholder = '<$key>';
if (i + placeholder.length <= line.length &&
line.substring(i, i + placeholder.length) == placeholder) {
// Found a placeholder
if (braceDepth > 0) {
// Inside a parameter block - use raw value
result.write(example[key]);
} else {
// Outside parameter blocks - wrap with {}
result.write('{${example[key]}}');
}
i += placeholder.length;
foundPlaceholder = true;
break;
}
}
if (!foundPlaceholder) {
result.write(line[i]);
i++;
}
} else {
result.write(line[i]);
i++;
}
}
return replaced;

return result.toString();
}
112 changes: 112 additions & 0 deletions test/data_tables_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,118 @@ void main() {
});
});
}
''';

final feature = FeatureFile(
featureDir: 'test.feature',
package: 'test',
input: featureFile,
);
expect(feature.dartContent, expectedFeatureDart);
});

test('Data table with single row (headers only)', () {
const featureFile = '''
Feature: Testing feature
Scenario: Testing scenario
Given the following songs
| 'artist' | 'title' |
''';

const expectedFeatureDart = '''
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning

import 'package:bdd_widget_test/data_table.dart' as bdd;
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

import './step/the_following_songs.dart';

void main() {
group(\'\'\'Testing feature\'\'\', () {
testWidgets(\'\'\'Testing scenario\'\'\', (tester) async {
await theFollowingSongs(tester, const bdd.DataTable([['artist', 'title']]));
});
});
}
''';

final feature = FeatureFile(
featureDir: 'test.feature',
package: 'test',
input: featureFile,
);
expect(feature.dartContent, expectedFeatureDart);
});

test('Data table with special characters in cells', () {
const featureFile = '''
Feature: Testing feature
Scenario: Testing scenario
Given the following data
| 'name' | 'description' |
| 'Test "One"' | 'Has quotes' |
| 'Test <Two>' | 'Has angle brackets' |
| 'Test {3}' | 'Has braces' |
| 'Test, Four' | 'Has comma' |
''';

const expectedFeatureDart = '''
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning

import 'package:bdd_widget_test/data_table.dart' as bdd;
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

import './step/the_following_data.dart';

void main() {
group(\'\'\'Testing feature\'\'\', () {
testWidgets(\'\'\'Testing scenario\'\'\', (tester) async {
await theFollowingData(tester, const bdd.DataTable([['name', 'description'], ['Test "One"', 'Has quotes'], ['Test <Two>', 'Has angle brackets'], ['Test {3}', 'Has braces'], ['Test, Four', 'Has comma']]));
});
});
}
''';

final feature = FeatureFile(
featureDir: 'test.feature',
package: 'test',
input: featureFile,
);
expect(feature.dartContent, expectedFeatureDart);
});

test('Data table with unicode and emoji', () {
const featureFile = '''
Feature: Testing feature
Scenario: Testing scenario
Given the following items
| 'emoji' | 'description' |
| '🚀' | 'Rocket' |
| '💯' | 'Perfect' |
| 'Ñoño' | 'Spanish' |
''';

const expectedFeatureDart = '''
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning

import 'package:bdd_widget_test/data_table.dart' as bdd;
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

import './step/the_following_items.dart';

void main() {
group(\'\'\'Testing feature\'\'\', () {
testWidgets(\'\'\'Testing scenario\'\'\', (tester) async {
await theFollowingItems(tester, const bdd.DataTable([['emoji', 'description'], ['🚀', 'Rocket'], ['💯', 'Perfect'], ['Ñoño', 'Spanish']]));
});
});
}
''';

final feature = FeatureFile(
Expand Down
99 changes: 85 additions & 14 deletions test/feature_test.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'package:bdd_widget_test/src/feature_file.dart';
import 'package:bdd_widget_test/src/generator_options.dart';
import 'package:test/test.dart';

import 'util/testing_data.dart';
Expand Down Expand Up @@ -108,18 +107,49 @@ void main() {
);
expect(feature.dartContent, expectedFeatureDart);
});
test('custom headers replace default imports in feature file', () async {

test('Feature with special characters in names', () {
const expectedFeatureDart = '''
${expectedComment}import 'package:patrol/patrol.dart';
// Import flutter_test for compatibility
import 'package:flutter_test/flutter_test.dart';
$expectedComment// some comment

${expectedImports}import './step/the_app_is_running.dart';
import './step/i_see_text.dart';

void main() {
group(\'\'\'"Testing" <Special> {Characters}\'\'\', () {
testWidgets(\'\'\'Test's "special" characters\'\'\', (tester) async {
await theAppIsRunning(tester);
await iSeeText(tester, 'test');
});
});
}
''';

import './step/the_app_is_running.dart';
final feature = FeatureFile(
featureDir: 'test.feature',
package: 'test',
input: '''
// some comment

Feature: "Testing" <Special> {Characters}
Scenario: Test's "special" characters
Given the app is running
Then I see {'test'} text
''',
);
expect(feature.dartContent, expectedFeatureDart);
});

test('Feature with very long step description', () {
const expectedFeatureDart = '''
${expectedHeader}import './step/the_app_is_running.dart';
import './step/i_verify_that_this_is_a_very_long_step_description_that_tests_whether_the_framework_can_handle_extremely_long_step_names_without_issues.dart';

void main() {
group(\'\'\'Testing feature\'\'\', () {
testWidgets(\'\'\'Testing scenario\'\'\', (tester) async {
await theAppIsRunning(tester);
await iVerifyThatThisIsAVeryLongStepDescriptionThatTestsWhetherTheFrameworkCanHandleExtremelyLongStepNamesWithoutIssues(tester);
});
});
}
Expand All @@ -128,14 +158,55 @@ void main() {
final feature = FeatureFile(
featureDir: 'test.feature',
package: 'test',
input: minimalFeatureFile,
generatorOptions: const GeneratorOptions(
customHeaders: [
"import 'package:patrol/patrol.dart';",
'// Import flutter_test for compatibility',
"import 'package:flutter_test/flutter_test.dart';",
],
),
input: '''
Feature: Testing feature
Scenario: Testing scenario
Given the app is running
Then I verify that this is a very long step description that tests whether the framework can handle extremely long step names without issues
''',
);
expect(feature.dartContent, expectedFeatureDart);
});

test('Multiple scenarios in single feature', () {
const expectedFeatureDart = '''
${expectedHeader}import './step/the_app_is_running.dart';
import './step/i_see_text.dart';
import './step/i_tap_icon.dart';

void main() {
group(\'\'\'Login feature\'\'\', () {
testWidgets(\'\'\'Successful login\'\'\', (tester) async {
await theAppIsRunning(tester);
await iSeeText(tester, 'Login');
});
testWidgets(\'\'\'Failed login\'\'\', (tester) async {
await theAppIsRunning(tester);
await iSeeText(tester, 'Error');
});
testWidgets(\'\'\'Logout\'\'\', (tester) async {
await iTapIcon(tester, Icons.logout);
});
});
}
''';

final feature = FeatureFile(
featureDir: 'test.feature',
package: 'test',
input: '''
Feature: Login feature
Scenario: Successful login
Given the app is running
Then I see {'Login'} text

Scenario: Failed login
Given the app is running
Then I see {'Error'} text

Scenario: Logout
When I tap {Icons.logout} icon
''',
);
expect(feature.dartContent, expectedFeatureDart);
});
Expand Down
Loading