From 9d6ecf0c050f886c3cff8cd069260e1ecf482a94 Mon Sep 17 00:00:00 2001 From: Chris Rudolphi <1702962+clrudolphi@users.noreply.github.com> Date: Wed, 22 Oct 2025 15:31:38 -0500 Subject: [PATCH 1/3] Snippet generator honors the StepDefinitionSkeletonStyle setting from the reqnroll.json file when generating snippets (sync or async). Added unit tests for the new skeletonStyle enum deserialization. Added unit test for the SnippetService. Added Spec test in the DefineStepsCommand spec for the generation of async and sync snippets. --- .../ReqnrollConfigDeserializer.cs | 12 +++-- .../CucumberExpressionSkeletonProvider.cs | 4 +- .../DeveroomStepDefinitionSkeletonProvider.cs | 7 ++- .../RegexStepDefinitionSkeletonProvider.cs | 2 +- .../Snippets/SnippetExpressionStyle.cs | 23 +++++++- .../Snippets/SnippetService.cs | 6 +-- .../Commands/DefineStepsCommand.feature | 41 ++++++++++++++ .../StepDefinitions/ProjectSystemSteps.cs | 4 +- .../ReqnrollConfigDeserializerTests.cs | 34 ++++++++++++ .../Snippets/SnippetServiceTests.cs | 53 +++++++++++++++++++ 10 files changed, 172 insertions(+), 14 deletions(-) create mode 100644 Tests/Reqnroll.VisualStudio.Tests/Snippets/SnippetServiceTests.cs diff --git a/Reqnroll.VisualStudio/Configuration/ReqnrollConfigDeserializer.cs b/Reqnroll.VisualStudio/Configuration/ReqnrollConfigDeserializer.cs index 2f4c49a5..0bbe3419 100644 --- a/Reqnroll.VisualStudio/Configuration/ReqnrollConfigDeserializer.cs +++ b/Reqnroll.VisualStudio/Configuration/ReqnrollConfigDeserializer.cs @@ -21,10 +21,14 @@ public void Populate(string jsonString, DeveroomConfiguration config) config.ConfiguredBindingCulture = bindingCulture; if (reqnrollJsonConfiguration.Trace != null && reqnrollJsonConfiguration.Trace.TryGetValue("stepDefinitionSkeletonStyle", out var sdSnippetStyle)) { - if (sdSnippetStyle == "CucumberExpressionAttribute") - config.SnippetExpressionStyle = SnippetExpressionStyle.CucumberExpression; - if (sdSnippetStyle == "RegexAttribute") - config.SnippetExpressionStyle = SnippetExpressionStyle.RegularExpression; + config.SnippetExpressionStyle = sdSnippetStyle switch + { + "CucumberExpressionAttribute" => SnippetExpressionStyle.CucumberExpression, + "RegexAttribute" => SnippetExpressionStyle.RegularExpression, + "AsyncCucumberExpressionAttribute" => SnippetExpressionStyle.AsyncCucumberExpression, + "AsyncRegexAttribute" => SnippetExpressionStyle.AsyncRegularExpression, + _ => SnippetExpressionStyle.CucumberExpression + }; } } diff --git a/Reqnroll.VisualStudio/Snippets/Fallback/CucumberExpressionSkeletonProvider.cs b/Reqnroll.VisualStudio/Snippets/Fallback/CucumberExpressionSkeletonProvider.cs index 7e9b57de..66a22672 100644 --- a/Reqnroll.VisualStudio/Snippets/Fallback/CucumberExpressionSkeletonProvider.cs +++ b/Reqnroll.VisualStudio/Snippets/Fallback/CucumberExpressionSkeletonProvider.cs @@ -4,8 +4,8 @@ namespace Reqnroll.VisualStudio.Snippets.Fallback; public class CucumberExpressionSkeletonProvider : DeveroomStepDefinitionSkeletonProvider { - public CucumberExpressionSkeletonProvider(ReqnrollProjectTraits projectTraits) - : base(projectTraits) + public CucumberExpressionSkeletonProvider(ReqnrollProjectTraits projectTraits, bool useAsync) + : base(projectTraits, useAsync) { } diff --git a/Reqnroll.VisualStudio/Snippets/Fallback/DeveroomStepDefinitionSkeletonProvider.cs b/Reqnroll.VisualStudio/Snippets/Fallback/DeveroomStepDefinitionSkeletonProvider.cs index 11f0afa9..805de08a 100644 --- a/Reqnroll.VisualStudio/Snippets/Fallback/DeveroomStepDefinitionSkeletonProvider.cs +++ b/Reqnroll.VisualStudio/Snippets/Fallback/DeveroomStepDefinitionSkeletonProvider.cs @@ -6,10 +6,12 @@ public abstract class DeveroomStepDefinitionSkeletonProvider { protected ReqnrollProjectTraits ProjectTraits { get; } protected abstract bool UseVerbatimStringForExpression { get; } + protected bool UseAsync { get; } - protected DeveroomStepDefinitionSkeletonProvider(ReqnrollProjectTraits projectTraits) + protected DeveroomStepDefinitionSkeletonProvider(ReqnrollProjectTraits projectTraits, bool useAsync) { ProjectTraits = projectTraits; + UseAsync = useAsync; } public string GetStepDefinitionSkeletonSnippet(UndefinedStepDescriptor undefinedStep, @@ -23,9 +25,10 @@ public string GetStepDefinitionSkeletonSnippet(UndefinedStepDescriptor undefined var methodName = GetMethodName(undefinedStep, analyzedStepText); var parameters = string.Join(", ", analyzedStepText.Parameters.Select(ToDeclaration)); var stringPrefix = UseVerbatimStringForExpression ? "@" : ""; + var returnSignature = UseAsync ? "async Task" : "void"; var method = $"[{undefinedStep.ScenarioBlock}({stringPrefix}\"{regex}\")]" + newLine + - $"public void {methodName}({parameters})" + newLine + + $"public {returnSignature} {methodName}{(UseAsync ? "Async" : "")}({parameters})" + newLine + "{" + newLine + $"{indent}throw new PendingStepException();" + newLine + "}" + newLine; diff --git a/Reqnroll.VisualStudio/Snippets/Fallback/RegexStepDefinitionSkeletonProvider.cs b/Reqnroll.VisualStudio/Snippets/Fallback/RegexStepDefinitionSkeletonProvider.cs index 19979628..16e18933 100644 --- a/Reqnroll.VisualStudio/Snippets/Fallback/RegexStepDefinitionSkeletonProvider.cs +++ b/Reqnroll.VisualStudio/Snippets/Fallback/RegexStepDefinitionSkeletonProvider.cs @@ -2,7 +2,7 @@ namespace Reqnroll.VisualStudio.Snippets.Fallback; public class RegexStepDefinitionSkeletonProvider : DeveroomStepDefinitionSkeletonProvider { - public RegexStepDefinitionSkeletonProvider(ReqnrollProjectTraits projectTraits) : base(projectTraits) + public RegexStepDefinitionSkeletonProvider(ReqnrollProjectTraits projectTraits, bool useAsync) : base(projectTraits, useAsync) { } diff --git a/Reqnroll.VisualStudio/Snippets/SnippetExpressionStyle.cs b/Reqnroll.VisualStudio/Snippets/SnippetExpressionStyle.cs index 0f908339..31811ae7 100644 --- a/Reqnroll.VisualStudio/Snippets/SnippetExpressionStyle.cs +++ b/Reqnroll.VisualStudio/Snippets/SnippetExpressionStyle.cs @@ -3,5 +3,26 @@ namespace Reqnroll.VisualStudio.Snippets; public enum SnippetExpressionStyle { RegularExpression, - CucumberExpression + CucumberExpression, + AsyncRegularExpression, + AsyncCucumberExpression } + +public static class SnippetExpressionStyleExtensions +{ + public static bool IsAsync(this SnippetExpressionStyle style) + { + if (style == SnippetExpressionStyle.AsyncRegularExpression + || style == SnippetExpressionStyle.AsyncCucumberExpression) + return true; + return false; + } + + public static bool IsCucumber(this SnippetExpressionStyle style) + { + if (style == SnippetExpressionStyle.CucumberExpression + || style == SnippetExpressionStyle.AsyncCucumberExpression) + return true; + return false; + } +} \ No newline at end of file diff --git a/Reqnroll.VisualStudio/Snippets/SnippetService.cs b/Reqnroll.VisualStudio/Snippets/SnippetService.cs index 9a5c9f1f..a3748c22 100644 --- a/Reqnroll.VisualStudio/Snippets/SnippetService.cs +++ b/Reqnroll.VisualStudio/Snippets/SnippetService.cs @@ -21,9 +21,9 @@ public string GetStepDefinitionSkeletonSnippet(UndefinedStepDescriptor undefined try { var projectTraits = _projectScope.GetProjectSettings().ReqnrollProjectTraits; - var skeletonProvider = expressionStyle == SnippetExpressionStyle.CucumberExpression - ? (DeveroomStepDefinitionSkeletonProvider) new CucumberExpressionSkeletonProvider(projectTraits) - : new RegexStepDefinitionSkeletonProvider(projectTraits); + var skeletonProvider = expressionStyle.IsCucumber() + ? (DeveroomStepDefinitionSkeletonProvider) new CucumberExpressionSkeletonProvider(projectTraits, expressionStyle.IsAsync()) + : new RegexStepDefinitionSkeletonProvider(projectTraits, expressionStyle.IsAsync()); var configuration = _projectScope.GetDeveroomConfiguration(); newLine = newLine ?? Environment.NewLine; diff --git a/Tests/Reqnroll.VisualStudio.Specs/Features/Editor/Commands/DefineStepsCommand.feature b/Tests/Reqnroll.VisualStudio.Specs/Features/Editor/Commands/DefineStepsCommand.feature index 2e3ebe70..5ed2aaa0 100644 --- a/Tests/Reqnroll.VisualStudio.Specs/Features/Editor/Commands/DefineStepsCommand.feature +++ b/Tests/Reqnroll.VisualStudio.Specs/Features/Editor/Commands/DefineStepsCommand.feature @@ -163,3 +163,44 @@ Scenario: DefineSteps command properly escapes empty brackets when using Regex e | 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": { "stepDefinitionSkeletonStyle": "AsyncRegexAttribute" } + } + """ + 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": { "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 + | Method | + | MyProject.StepDefinitions1.GivenTheClientHasABasket | \ No newline at end of file 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.Tests/Configuration/ReqnrollConfigDeserializerTests.cs b/Tests/Reqnroll.VisualStudio.Tests/Configuration/ReqnrollConfigDeserializerTests.cs index dcbb9880..52e4845f 100644 --- a/Tests/Reqnroll.VisualStudio.Tests/Configuration/ReqnrollConfigDeserializerTests.cs +++ b/Tests/Reqnroll.VisualStudio.Tests/Configuration/ReqnrollConfigDeserializerTests.cs @@ -2,6 +2,7 @@ using FluentAssertions; using Reqnroll.VisualStudio.Configuration; +using Reqnroll.VisualStudio.Snippets; using Xunit; namespace Reqnroll.VisualStudio.Tests.Configuration; @@ -149,4 +150,37 @@ public void Should_prioritize_language_binding_over_legacy_bindingCulture() config.ConfiguredBindingCulture.Should().Be("fr-FR"); // language.binding takes priority config.BindingCulture.Should().Be("fr-FR"); } + + [Theory] + [InlineData("RegexAttribute", SnippetExpressionStyle.RegularExpression)] + [InlineData("CucumberExpressionAttribute", SnippetExpressionStyle.CucumberExpression)] + [InlineData("AsyncRegexAttribute", SnippetExpressionStyle.AsyncRegularExpression)] + [InlineData("AsyncCucumberExpressionAttribute", SnippetExpressionStyle.AsyncCucumberExpression)] + [InlineData("InvalidValue", SnippetExpressionStyle.CucumberExpression)] // Default fallback + [InlineData("", SnippetExpressionStyle.CucumberExpression)] // Default fallback + [InlineData(null, SnippetExpressionStyle.CucumberExpression)] // Default fallback + public void Should_set_stepDefinitionSkeletonStyle_from_reqnroll_json(string styleValue, SnippetExpressionStyle expectedStyle) + { + // Arrange + var deserializer = new ReqnrollConfigDeserializer(); + var config = new DeveroomConfiguration(); + var styleJson = styleValue != null + ? $@" + {{ + ""trace"": {{ + ""stepDefinitionSkeletonStyle"": ""{styleValue}"" + }} + }}" + : @" + { + ""trace"": { + } + }"; + + // Act + deserializer.Populate(styleJson, config); + + // Assert + config.SnippetExpressionStyle.Should().Be(expectedStyle); + } } \ No newline at end of file diff --git a/Tests/Reqnroll.VisualStudio.Tests/Snippets/SnippetServiceTests.cs b/Tests/Reqnroll.VisualStudio.Tests/Snippets/SnippetServiceTests.cs new file mode 100644 index 00000000..3b043e63 --- /dev/null +++ b/Tests/Reqnroll.VisualStudio.Tests/Snippets/SnippetServiceTests.cs @@ -0,0 +1,53 @@ +using FluentAssertions; +using NSubstitute; +using Reqnroll.VisualStudio.Snippets; +using Reqnroll.VisualStudio.ProjectSystem; +using Reqnroll.VisualStudio.ProjectSystem.Configuration; +using Xunit; + +namespace Reqnroll.VisualStudio.Tests.Snippets +{ + public class SnippetServiceTests + { + private readonly IProjectScope _projectScope; + private readonly IIdeScope _ideScope; + private readonly IDeveroomLogger _logger; + private readonly SnippetService _service; + private readonly DeveroomConfiguration _defaultDeveroomConfig; + private readonly ITestOutputHelper testOutputHelper; + + public SnippetServiceTests(ITestOutputHelper testOutputHelper) + { + _ideScope = new StubIdeScope(testOutputHelper); + _logger = new StubLogger(); + _projectScope = new StubProjectScope(@"C:\", "bin", _ideScope, new List(), "net8"); + + _defaultDeveroomConfig = new DeveroomConfiguration(); + + // Construct the service with the substitute + _service = new SnippetService(_projectScope); + this.testOutputHelper = testOutputHelper; + } + + [Theory] + [InlineData(SnippetExpressionStyle.RegularExpression, "[Given(@\"pattern\")]\npublic void GivenPattern()\n{\nthrow new PendingStepException();\n}\n")] + [InlineData(SnippetExpressionStyle.AsyncRegularExpression, "[Given(@\"pattern\")]\npublic async Task GivenPatternAsync()\n{\nthrow new PendingStepException();\n}\n")] + [InlineData(SnippetExpressionStyle.CucumberExpression, "[Given(\"pattern\")]\npublic void GivenPattern()\n{\nthrow new PendingStepException();\n}\n")] + [InlineData(SnippetExpressionStyle.AsyncCucumberExpression, "[Given(\"pattern\")]\npublic async Task GivenPatternAsync()\n{\nthrow new PendingStepException();\n}\n")] + public void Generates_correct_step_definition_snippet(SnippetExpressionStyle style, string expectedSnippet) + { + // Arrange + var undefinedStep = new DeveroomGherkinStep(new Gherkin.Ast.Location(0, 0), "Given ", Gherkin.StepKeywordType.Context, "pattern", null, StepKeyword.Given, ScenarioBlock.Given); + var undefinedStepDescriptor = new UndefinedStepDescriptor(undefinedStep, "pattern"); + var indent = ""; + var newLine = "\n"; + + // Act + var snippet = _service.GetStepDefinitionSkeletonSnippet( + undefinedStepDescriptor, style, indent, newLine); + + // Assert + snippet.Should().Be(expectedSnippet); + } + } +} \ No newline at end of file From 56380c4196bd714f63ed5aebd0d6fba8372740fd Mon Sep 17 00:00:00 2001 From: Chris Rudolphi <1702962+clrudolphi@users.noreply.github.com> Date: Wed, 22 Oct 2025 15:36:16 -0500 Subject: [PATCH 2/3] Updated Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d9b9a63..f44ba67b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Improvements: +* The 'Define Steps' command honors the StepDefinitionSkeletonStyle setting in the project reqnroll.json configuration file and will generate step skeletons using 'Async' appropriately. + ## Bug fixes: *Contributors of this release (in alphabetical order):* From a22516d642dd9f303e56dd16aae4ab90c00c9036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1sp=C3=A1r=20Nagy?= Date: Wed, 17 Dec 2025 16:40:53 +0100 Subject: [PATCH 3/3] Add PR number to CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f7f2076..c106f646 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ## Improvements: -* The 'Define Steps' command honors the StepDefinitionSkeletonStyle setting in the project reqnroll.json configuration file and will generate step skeletons using 'Async' appropriately. +* The 'Define Steps' command honors the StepDefinitionSkeletonStyle setting in the project reqnroll.json configuration file and will generate step skeletons using 'Async' appropriately. (#129) * Update docs - .NET 10, TUnit, VS2026 (#138) ## Bug fixes: