diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0265dcdc..0c97efb9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
* New Project Wizard references the latest versions of supported test frameworks and .NET frameworks
* Format Document will now right-align numeric values in tables. This can be overridden to left align them by setting `gherkin_table_cell_right_align_numeric_content = false` in .editorconfig file within a `[*.feature]` section.
+* Define Steps Command now supports generating async method definitions.
## Bug fixes:
diff --git a/Reqnroll.VisualStudio.UI/Dialogs/CreateStepDefinitionsDialog.xaml b/Reqnroll.VisualStudio.UI/Dialogs/CreateStepDefinitionsDialog.xaml
index 524459d0..365f2165 100644
--- a/Reqnroll.VisualStudio.UI/Dialogs/CreateStepDefinitionsDialog.xaml
+++ b/Reqnroll.VisualStudio.UI/Dialogs/CreateStepDefinitionsDialog.xaml
@@ -55,6 +55,9 @@
Regular Expressions
Cucumber Expressions
-->
+
diff --git a/Reqnroll.VisualStudio/Configuration/DeveroomConfiguration.cs b/Reqnroll.VisualStudio/Configuration/DeveroomConfiguration.cs
index 084582d9..8bda616d 100644
--- a/Reqnroll.VisualStudio/Configuration/DeveroomConfiguration.cs
+++ b/Reqnroll.VisualStudio/Configuration/DeveroomConfiguration.cs
@@ -21,7 +21,7 @@ public class DeveroomConfiguration
public string ConfiguredBindingCulture { get; set; } = null;
public string BindingCulture => ConfiguredBindingCulture ?? DefaultFeatureLanguage;
public SnippetExpressionStyle SnippetExpressionStyle { get; set; } = SnippetExpressionStyle.CucumberExpression;
-
+ public bool GenerateAsyncSkeletonMethods { get; set; } = true;
private void FixEmptyContainers()
{
diff --git a/Reqnroll.VisualStudio/Configuration/ReqnrollConfigDeserializer.cs b/Reqnroll.VisualStudio/Configuration/ReqnrollConfigDeserializer.cs
index 2f4c49a5..863171c1 100644
--- a/Reqnroll.VisualStudio/Configuration/ReqnrollConfigDeserializer.cs
+++ b/Reqnroll.VisualStudio/Configuration/ReqnrollConfigDeserializer.cs
@@ -26,6 +26,12 @@ public void Populate(string jsonString, DeveroomConfiguration config)
if (sdSnippetStyle == "RegexAttribute")
config.SnippetExpressionStyle = SnippetExpressionStyle.RegularExpression;
}
+ if (reqnrollJsonConfiguration.Trace != null &&
+ reqnrollJsonConfiguration.Trace.TryGetValue("generateStepDefinitionSkeletonAsAsync", out var generateStepDefinitionSkeletonAsAsync) &&
+ bool.TryParse(generateStepDefinitionSkeletonAsAsync, out bool generateAsyncSkeletonMethods))
+ {
+ config.GenerateAsyncSkeletonMethods = generateAsyncSkeletonMethods;
+ }
}
private class ReqnrollJsonConfiguration
diff --git a/Reqnroll.VisualStudio/Editor/Commands/DefineStepsCommand.cs b/Reqnroll.VisualStudio/Editor/Commands/DefineStepsCommand.cs
index 507d804f..1d2e8ab5 100644
--- a/Reqnroll.VisualStudio/Editor/Commands/DefineStepsCommand.cs
+++ b/Reqnroll.VisualStudio/Editor/Commands/DefineStepsCommand.cs
@@ -63,24 +63,22 @@ public override bool PreExec(IWpfTextView textView, DeveroomEditorCommandTargetK
const string indent = " ";
string newLine = Environment.NewLine;
- var feature = (Feature) featureTag.Data;
- var viewModel = new CreateStepDefinitionsDialogViewModel();
- viewModel.ClassName = feature.Name.ToIdentifier() + "StepDefinitions";
- viewModel.ExpressionStyle = snippetService.DefaultExpressionStyle;
-
- foreach (var undefinedStepTag in undefinedStepTags)
+ var feature = (Feature)featureTag.Data;
+ var viewModel = new CreateStepDefinitionsDialogViewModel
{
- var matchResult = (MatchResult) undefinedStepTag.Data;
- foreach (var match in matchResult.Items.Where(mi => mi.Type == MatchResultType.Undefined))
- {
- var snippet = snippetService.GetStepDefinitionSkeletonSnippet(match.UndefinedStep,
- viewModel.ExpressionStyle, indent, newLine);
- if (viewModel.Items.Any(i => i.Snippet == snippet))
- continue;
-
- viewModel.Items.Add(new StepDefinitionSnippetItemViewModel {Snippet = snippet});
- }
- }
+ IsInitializing = true,
+ Generator = GenerateSnippets,
+ ClassName = feature.Name.ToIdentifier() + "StepDefinitions",
+ ExpressionStyle = snippetService.DefaultExpressionStyle,
+ GenerateAsyncMethods = snippetService.DefaultGenerateSkeletonMethodsAsAsync,
+ Indent = indent,
+ NewLine = newLine,
+ UndefinedStepTags = undefinedStepTags,
+ SnippetService = snippetService
+ };
+ viewModel.IsInitializing = false;
+
+ viewModel.Items = new ObservableCollection(GenerateSnippets(viewModel));
IdeScope.WindowManager.ShowDialog(viewModel);
@@ -113,6 +111,25 @@ public override bool PreExec(IWpfTextView textView, DeveroomEditorCommandTargetK
return true;
}
+ public IEnumerable GenerateSnippets(CreateStepDefinitionsDialogViewModel viewModel)
+ {
+ var result = new List();
+ foreach (var undefinedStepTag in viewModel.UndefinedStepTags)
+ {
+ var matchResult = (MatchResult)undefinedStepTag.Data;
+ foreach (var match in matchResult.Items.Where(mi => mi.Type == MatchResultType.Undefined))
+ {
+ var snippet = viewModel.SnippetService.GetStepDefinitionSkeletonSnippet(match.UndefinedStep,
+ viewModel.ExpressionStyle, viewModel.GenerateAsyncMethods, viewModel.Indent, viewModel.NewLine);
+ if (result.Any(i => i.Snippet == snippet))
+ continue;
+
+ result.Add(new StepDefinitionSnippetItemViewModel { Snippet = snippet });
+ }
+ }
+ return result;
+ }
+
private void SaveAsStepDefinitionClass(IProjectScope projectScope, string combinedSnippet, string className,
string indent, string newLine)
{
diff --git a/Reqnroll.VisualStudio/Editor/Commands/GoToDefinitionCommand.cs b/Reqnroll.VisualStudio/Editor/Commands/GoToDefinitionCommand.cs
index aeee3bb3..7d0fdbce 100644
--- a/Reqnroll.VisualStudio/Editor/Commands/GoToDefinitionCommand.cs
+++ b/Reqnroll.VisualStudio/Editor/Commands/GoToDefinitionCommand.cs
@@ -88,7 +88,7 @@ private void PerformOfferCopySnippet(MatchResultItem match, ITextBuffer textBuff
string newLine = Environment.NewLine;
var snippet = snippetService.GetStepDefinitionSkeletonSnippet(match.UndefinedStep,
- snippetService.DefaultExpressionStyle, indent, newLine);
+ snippetService.DefaultExpressionStyle, snippetService.DefaultGenerateSkeletonMethodsAsAsync, indent, newLine);
IdeScope.Actions.ShowQuestion(new QuestionDescription(GoToStepDefinitionsPopupHeader,
$"The step is undefined. Do you want to copy a step definition skeleton snippet to the clipboard?{Environment.NewLine}{Environment.NewLine}{snippet}",
diff --git a/Reqnroll.VisualStudio/Snippets/Fallback/DeveroomStepDefinitionSkeletonProvider.cs b/Reqnroll.VisualStudio/Snippets/Fallback/DeveroomStepDefinitionSkeletonProvider.cs
index 11f0afa9..538eedf9 100644
--- a/Reqnroll.VisualStudio/Snippets/Fallback/DeveroomStepDefinitionSkeletonProvider.cs
+++ b/Reqnroll.VisualStudio/Snippets/Fallback/DeveroomStepDefinitionSkeletonProvider.cs
@@ -13,7 +13,7 @@ protected DeveroomStepDefinitionSkeletonProvider(ReqnrollProjectTraits projectTr
}
public string GetStepDefinitionSkeletonSnippet(UndefinedStepDescriptor undefinedStep,
- string indent, string newLine, string bindingCultureName)
+ string indent, bool generateAsyncSnippet, string newLine, string bindingCultureName)
{
var bindingCulture = CultureInfo.GetCultureInfo(bindingCultureName);
@@ -23,9 +23,10 @@ public string GetStepDefinitionSkeletonSnippet(UndefinedStepDescriptor undefined
var methodName = GetMethodName(undefinedStep, analyzedStepText);
var parameters = string.Join(", ", analyzedStepText.Parameters.Select(ToDeclaration));
var stringPrefix = UseVerbatimStringForExpression ? "@" : "";
+ var returnSignature = generateAsyncSnippet ? "async Task" : "void";
var method = $"[{undefinedStep.ScenarioBlock}({stringPrefix}\"{regex}\")]" + newLine +
- $"public void {methodName}({parameters})" + newLine +
+ $"public {returnSignature} {methodName}{(generateAsyncSnippet ? "Async" : "")}({parameters})" + newLine +
"{" + newLine +
$"{indent}throw new PendingStepException();" + newLine +
"}" + newLine;
diff --git a/Reqnroll.VisualStudio/Snippets/SnippetService.cs b/Reqnroll.VisualStudio/Snippets/SnippetService.cs
index 9a5c9f1f..348ae470 100644
--- a/Reqnroll.VisualStudio/Snippets/SnippetService.cs
+++ b/Reqnroll.VisualStudio/Snippets/SnippetService.cs
@@ -14,9 +14,10 @@ public SnippetService(IProjectScope projectScope)
public SnippetExpressionStyle DefaultExpressionStyle =>
_projectScope.GetDeveroomConfiguration().SnippetExpressionStyle;
+ public bool DefaultGenerateSkeletonMethodsAsAsync => _projectScope.GetDeveroomConfiguration().GenerateAsyncSkeletonMethods;
public string GetStepDefinitionSkeletonSnippet(UndefinedStepDescriptor undefinedStep,
- SnippetExpressionStyle expressionStyle, string indent = " ", string newLine = null)
+ SnippetExpressionStyle expressionStyle, bool generateAsyncSkeletonSnippet, string indent = " ", string newLine = null)
{
try
{
@@ -28,7 +29,7 @@ public string GetStepDefinitionSkeletonSnippet(UndefinedStepDescriptor undefined
var configuration = _projectScope.GetDeveroomConfiguration();
newLine = newLine ?? Environment.NewLine;
var result =
- skeletonProvider.GetStepDefinitionSkeletonSnippet(undefinedStep, indent, newLine,
+ skeletonProvider.GetStepDefinitionSkeletonSnippet(undefinedStep, indent, generateAsyncSkeletonSnippet, newLine,
configuration.BindingCulture);
_logger.LogInfo(
$"Step definition snippet generated for step '{undefinedStep.StepText}': {Environment.NewLine}{result}");
diff --git a/Reqnroll.VisualStudio/UI/ViewModels/CreateStepDefinitionsDialogViewModel.cs b/Reqnroll.VisualStudio/UI/ViewModels/CreateStepDefinitionsDialogViewModel.cs
index f9ab5ac6..5785468a 100644
--- a/Reqnroll.VisualStudio/UI/ViewModels/CreateStepDefinitionsDialogViewModel.cs
+++ b/Reqnroll.VisualStudio/UI/ViewModels/CreateStepDefinitionsDialogViewModel.cs
@@ -1,17 +1,21 @@
#nullable disable
+using Reqnroll.VisualStudio.Snippets;
using System;
using System.Linq;
+using System.ComponentModel;
+using System.Collections.ObjectModel;
+using System.Runtime.CompilerServices;
namespace Reqnroll.VisualStudio.UI.ViewModels;
-public class CreateStepDefinitionsDialogViewModel
+public class CreateStepDefinitionsDialogViewModel : INotifyPropertyChanged
{
#if DEBUG
public static CreateStepDefinitionsDialogViewModel DesignData = new()
{
ClassName = "MyFeatureSteps",
ExpressionStyle = SnippetExpressionStyle.CucumberExpression,
- Items = new List
+ Items = new ObservableCollection
{
new()
{
@@ -33,6 +37,38 @@ public void GivenThereIsASimpleReqnrollProjectForVersion(Version reqnrollVersion
{
Snippet = @"[When(@""there is a simple Reqnroll project for (.*)"")]
public void GivenThereIsASimpleReqnrollProjectForVersion(Version reqnrollVersion)
+{
+ throw new PendingStepException();
+}"
+ }
+ }
+ };
+ public static CreateStepDefinitionsDialogViewModel DesignDataAsync = new()
+ {
+ ClassName = "MyFeatureSteps",
+ ExpressionStyle = SnippetExpressionStyle.CucumberExpression,
+ Items = new ObservableCollection
+ {
+ new()
+ {
+ Snippet = @"[Given(@""there is a simple Reqnroll project for (.*)"")]
+public async Task GivenThereIsASimpleReqnrollProjectForVersionAsync(Version reqnrollVersion)
+{
+ throw new PendingStepException();
+}"
+ },
+ new()
+ {
+ Snippet = @"[When(@""there is a simple Reqnroll project for (.*)"")]
+public async Task GivenThereIsASimpleReqnrollProjectForVersionAsync(Version reqnrollVersion)
+{
+ throw new PendingStepException();
+}"
+ },
+ new()
+ {
+ Snippet = @"[When(@""there is a simple Reqnroll project for (.*)"")]
+public async Task GivenThereIsASimpleReqnrollProjectForVersionAsync(Version reqnrollVersion)
{
throw new PendingStepException();
}"
@@ -40,8 +76,50 @@ public void GivenThereIsASimpleReqnrollProjectForVersion(Version reqnrollVersion
}
};
#endif
+
public string ClassName { get; set; }
public SnippetExpressionStyle ExpressionStyle { get; set; }
- public List Items { get; set; } = new();
+ private bool _generateAsyncMethods = true;
+ public bool GenerateAsyncMethods
+ {
+ get => _generateAsyncMethods;
+ set
+ {
+ if (_generateAsyncMethods != value)
+ {
+ _generateAsyncMethods = value;
+ OnPropertyChanged(nameof(GenerateAsyncMethods));
+ if (!IsInitializing)
+ {
+ RegenerateItems(); // Regenerate when property changes
+ }
+ }
+ }
+ }
+ public ObservableCollection Items { get; set; } = new();
public CreateStepDefinitionsDialogResult Result { get; set; }
+ public Func> Generator { get; set; }
+ public DeveroomTag[] UndefinedStepTags { get; set; }
+ public SnippetService SnippetService { get; set; }
+ public string Indent { get; set; }
+ public string NewLine { get; set; }
+
+ public bool IsInitializing;
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ public void RegenerateItems()
+ {
+ Items.Clear();
+ foreach (var item in Generator(this))
+ {
+ Items.Add(item);
+ }
+ OnPropertyChanged(nameof(Items));
+ }
}
diff --git a/Tests/Reqnroll.VisualStudio.Specs/Features/Editor/Commands/DefineStepsCommand.feature b/Tests/Reqnroll.VisualStudio.Specs/Features/Editor/Commands/DefineStepsCommand.feature
index 2e3ebe70..cdb0b7bc 100644
--- a/Tests/Reqnroll.VisualStudio.Specs/Features/Editor/Commands/DefineStepsCommand.feature
+++ b/Tests/Reqnroll.VisualStudio.Specs/Features/Editor/Commands/DefineStepsCommand.feature
@@ -47,7 +47,7 @@ Scenario: Two undefined step has the same step definition skeleton
And the project is built and the initial binding discovery is performed
When I invoke the "Define Steps" command
Then the define steps dialog should be opened with the following step definition skeletons
- | type | expression |
+ | type | expression |
| Given | the operand {int} has been entered |
Scenario: All steps are defined
@@ -121,11 +121,11 @@ Scenario: DefineSteps command abides by reqnroll.json configuration for regex sk
"trace": { "stepDefinitionSkeletonStyle": "RegexAttribute" }
}
"""
- And the project is built and the initial binding discovery is performed
- When I invoke the "Define Steps" command
- Then the define steps dialog should be opened with the following step definition skeletons
- | type | expression |
- | Given | the client added (.*) pcs to the basket |
+ And the project is built and the initial binding discovery is performed
+ When I invoke the "Define Steps" command
+ Then the define steps dialog should be opened with the following step definition skeletons
+ | type | expression |
+ | Given | the client added (.*) pcs to the basket |
Scenario: DefineSteps command properly escapes empty brackets when using Cucumber expressions
Given there is a Reqnroll project scope
@@ -139,8 +139,8 @@ Scenario: DefineSteps command properly escapes empty brackets when using Cucumbe
And the project is built and the initial binding discovery is performed
When I invoke the "Define Steps" command
Then the define steps dialog should be opened with the following step definition skeletons
- | type | expression |
- | When | I use \\(parenthesis), \\{curly braces} and\\/or \\\ backslash |
+ | type | expression |
+ | When | I use \\(parenthesis), \\{curly braces} and\\/or \\\\ backslash |
Scenario: DefineSteps command properly escapes empty brackets when using Regex expressions
Given there is a Reqnroll project scope
@@ -160,6 +160,48 @@ Scenario: DefineSteps command properly escapes empty brackets when using Regex e
And the project is built and the initial binding discovery is performed
When I invoke the "Define Steps" command
Then the define steps dialog should be opened with the following step definition skeletons
- | type | expression |
- | When | I use \\(parenthesis\), \\{curly braces}, \\\ backslash, and/or \\. period |
+ | type | expression |
+ | When | I use \\(parenthesis\\), \\{curly braces}, \\\\ backslash, and/or \\. period |
+
+Scenario: DefineSteps command abides by reqnroll.json configuration for async method declaration
+ Given there is a Reqnroll project scope
+ And the following feature file in the editor
+ """
+ Feature: Feature Using Regex Style
+
+ Scenario: Client has a simple basket
+ Given the client has a basket
+ """
+ And the reqnroll.json configuration file contains
+ """
+ {
+ "trace": { "generateStepDefinitionSkeletonAsAsync": true }
+ }
+ """
+ And the project is built and the initial binding discovery is performed
+ When I invoke the "Define Steps" command
+ Then the define steps dialog should be opened with the following step definition skeletons
+ | Method |
+ | MyProject.StepDefinitions1.GivenTheClientHasABasketAsync |
+
+Scenario: DefineSteps command abides by reqnroll.json configuration for synchronous method declaration
+ Given there is a Reqnroll project scope
+ And the following feature file in the editor
+ """
+ Feature: Feature Using Regex Style
+
+ Scenario: Client has a simple basket
+ Given the client has a basket
+ """
+ And the reqnroll.json configuration file contains
+ """
+ {
+ "trace": { "generateStepDefinitionSkeletonAsAsync": false }
+ }
+ """
+ And the project is built and the initial binding discovery is performed
+ When I invoke the "Define Steps" command
+ Then the define steps dialog should be opened with the following step definition skeletons
+ | Method |
+ | MyProject.StepDefinitions1.GivenTheClientHasABasket |
diff --git a/Tests/Reqnroll.VisualStudio.Specs/StepDefinitions/ProjectSystemSteps.cs b/Tests/Reqnroll.VisualStudio.Specs/StepDefinitions/ProjectSystemSteps.cs
index 17bc2a84..67b71e42 100644
--- a/Tests/Reqnroll.VisualStudio.Specs/StepDefinitions/ProjectSystemSteps.cs
+++ b/Tests/Reqnroll.VisualStudio.Specs/StepDefinitions/ProjectSystemSteps.cs
@@ -797,7 +797,8 @@ private StepDefinitionSnippetData[] ParseSnippetsFromFile(string text,
{
Type = sd.Type,
Regex = sd.Regex,
- Expression = sd.Expression
+ Expression = sd.Expression,
+ Method = sd.Method
}).ToArray();
}
@@ -987,5 +988,6 @@ private class StepDefinitionSnippetData
public string Type { get; set; }
public string Regex { get; set; }
public string Expression { get; set; }
+ public string Method { get; set; }
}
}
diff --git a/Tests/Reqnroll.VisualStudio.UI.Tester/Reqnroll.VisualStudio.UI.Tester.csproj b/Tests/Reqnroll.VisualStudio.UI.Tester/Reqnroll.VisualStudio.UI.Tester.csproj
index fe9fa4d3..e20ba396 100644
--- a/Tests/Reqnroll.VisualStudio.UI.Tester/Reqnroll.VisualStudio.UI.Tester.csproj
+++ b/Tests/Reqnroll.VisualStudio.UI.Tester/Reqnroll.VisualStudio.UI.Tester.csproj
@@ -12,4 +12,19 @@
+
+
+
+ True
+ True
+ Settings.settings
+
+
+
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
\ No newline at end of file
diff --git a/Tests/Reqnroll.VisualStudio.UI.Tester/UiTesterWindow.xaml.cs b/Tests/Reqnroll.VisualStudio.UI.Tester/UiTesterWindow.xaml.cs
index 520dbf0f..d9925011 100644
--- a/Tests/Reqnroll.VisualStudio.UI.Tester/UiTesterWindow.xaml.cs
+++ b/Tests/Reqnroll.VisualStudio.UI.Tester/UiTesterWindow.xaml.cs
@@ -48,7 +48,25 @@ private void Test_ContextMenu_At100x100(object sender, RoutedEventArgs e)
private void Test_GenerateStepDefinitions(object sender, RoutedEventArgs e)
{
- var viewModel = CreateStepDefinitionsDialogViewModel.DesignData;
+ var syncViewModel = CreateStepDefinitionsDialogViewModel.DesignData;
+ var viewModel = new CreateStepDefinitionsDialogViewModel() {
+ ClassName = syncViewModel.ClassName,
+ ExpressionStyle = syncViewModel.ExpressionStyle };
+ viewModel.IsInitializing = true;
+ foreach (var item in syncViewModel.Items)
+ {
+ viewModel.Items.Add(item);
+ }
+ viewModel.GenerateAsyncMethods = false;
+ viewModel.Generator = (m) =>
+ {
+ if (m.GenerateAsyncMethods)
+ return CreateStepDefinitionsDialogViewModel.DesignDataAsync.Items.ToList();
+ else
+ return CreateStepDefinitionsDialogViewModel.DesignData.Items.ToList();
+ };
+ viewModel.IsInitializing = false;
+
var dialog = new CreateStepDefinitionsDialog(viewModel);
dialog.ShowDialog();