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):* 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