From b8b0213886604d91bb4c65d75f36716a20f25344 Mon Sep 17 00:00:00 2001 From: yanjustino Date: Tue, 11 Mar 2025 10:27:24 -0300 Subject: [PATCH] Refactor GherXunit structure and enhance lexer support: rename files, introduce IGherXunitLexer interface, and implement custom lexers for localization --- .github/workflows/dotnet.yml | 8 +- README.md | 81 ++++++++++++++++-- README_PTBR.md | 85 +++++++++++++++++-- src/GherXunit.sln | 9 -- src/base/GherXunit.Core/GherXunit.Core.csproj | 2 + src/base/GherXunit.Core/Interfaces.cs | 14 ++- src/base/GherXunit.Core/Methods.cs | 46 +++++----- .../Backgrounds/BackgroundTest.Context.cs | 0 .../Backgrounds/BackgroundTest.Steps.cs | 0 .../Backgrounds/BackgroundTest.cs | 0 .../Features/SubscriptionTest.Steps.cs | 0 .../Features/SubscriptionTest.cs | 0 .../Localization/LocalizationTest.Steps.cs} | 4 +- .../Samples/Localization/LocalizationTest.cs | 39 +++++++++ .../Outline/ScenarioOutlineTest.Steps.cs | 0 .../Samples/Outline/ScenarioOutlineTest.cs | 0 .../ExemploA/ProcessamentoServiceTests.cs | 74 ++++++++-------- .../Samples/Rules/RuleTest.Steps.cs | 0 .../GherXunit.Core}/Samples/Rules/RuleTest.cs | 0 src/base/GherXunit.Core/StepStringHandler.cs | 26 ------ src/base/GherXunit.Core/StringHandler.cs | 64 ++++++++++++++ .../GherXunit/GherXunit.Content.Interfaces.cs | 12 +++ .../GherXunit/GherXunit.Content.Methods.cs | 50 ++++++----- .../GherXunit.Content.StringHandler.cs | 56 ++++++++++-- src/sample/BddSample/BddSample.csproj | 27 ------ .../Backgrounds/BackgroundTest.Context.cs | 13 --- .../Backgrounds/BackgroundTest.Steps.cs | 49 ----------- .../Samples/Backgrounds/BackgroundTest.cs | 45 ---------- .../Samples/Features/SubscriptionTest.cs | 25 ------ 29 files changed, 413 insertions(+), 316 deletions(-) rename src/base/GherXunit.Core/{ => Samples}/Backgrounds/BackgroundTest.Context.cs (100%) rename src/base/GherXunit.Core/{ => Samples}/Backgrounds/BackgroundTest.Steps.cs (100%) rename src/base/GherXunit.Core/{ => Samples}/Backgrounds/BackgroundTest.cs (100%) rename src/base/GherXunit.Core/{ => Samples}/Features/SubscriptionTest.Steps.cs (100%) rename src/base/GherXunit.Core/{ => Samples}/Features/SubscriptionTest.cs (100%) rename src/{sample/BddSample/Samples/Features/SubscriptionTest.Steps.cs => base/GherXunit.Core/Samples/Localization/LocalizationTest.Steps.cs} (71%) create mode 100644 src/base/GherXunit.Core/Samples/Localization/LocalizationTest.cs rename src/{sample/BddSample => base/GherXunit.Core}/Samples/Outline/ScenarioOutlineTest.Steps.cs (100%) rename src/{sample/BddSample => base/GherXunit.Core}/Samples/Outline/ScenarioOutlineTest.cs (100%) rename src/{sample/BddSample => base/GherXunit.Core}/Samples/Refactoring/ExemploA/ProcessamentoServiceTests.cs (70%) rename src/{sample/BddSample => base/GherXunit.Core}/Samples/Rules/RuleTest.Steps.cs (100%) rename src/{sample/BddSample => base/GherXunit.Core}/Samples/Rules/RuleTest.cs (100%) delete mode 100644 src/base/GherXunit.Core/StepStringHandler.cs create mode 100644 src/base/GherXunit.Core/StringHandler.cs delete mode 100644 src/sample/BddSample/BddSample.csproj delete mode 100644 src/sample/BddSample/Samples/Backgrounds/BackgroundTest.Context.cs delete mode 100644 src/sample/BddSample/Samples/Backgrounds/BackgroundTest.Steps.cs delete mode 100644 src/sample/BddSample/Samples/Backgrounds/BackgroundTest.cs delete mode 100644 src/sample/BddSample/Samples/Features/SubscriptionTest.cs diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 7def053..2cb5ad7 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -7,18 +7,15 @@ on: branches: [ "main" ] jobs: - build: - strategy: matrix: - #configuration: [Debug, Release] configuration: [Release] runs-on: ubuntu-latest env: - Version: 1.2.${{ github.run_number }} + Version: 1.3.${{ github.run_number }} Solution_Path: ./src/GherXunit.sln Project_Path: ./src/lib/GherXunit/GherXunit.csproj Package_Path: ./src/lib/GherXunit/**/*. @@ -48,5 +45,4 @@ jobs: if: github.ref == 'refs/heads/main' run: | dotnet pack ${{env.Project_Path}} --no-restore --configuration ${{ matrix.configuration }} -p:PackageVersion=${{ env.Version }} - dotnet nuget push ${{env.Package_Path}}${{ env.Version }}.nupkg --api-key ${{ secrets.NUGET_KEY }} --source ${{ env.NUGET_INDEX }} - \ No newline at end of file + dotnet nuget push ${{env.Package_Path}}${{ env.Version }}.nupkg --api-key ${{ secrets.NUGET_KEY }} --source ${{ env.NUGET_INDEX }} \ No newline at end of file diff --git a/README.md b/README.md index 6ca7500..25f5b46 100644 --- a/README.md +++ b/README.md @@ -84,17 +84,82 @@ The result of running the test scenarios defined in the `SubscriptionTest` class ```gherkindotnet TEST RESULT: 🟢 SUCCESS ⤷ FEATURE Subscribers see different articles based on their subscription level - ⤷ SCENARIO Free subscribers see only the free articles - | GIVEN ↘ Free Frieda has a free subscription - | WHEN ↘ Free Frieda logs in with her valid credentials - | THEN ↘ she sees a Free article + ⤷ SCENARIO Free subscribers see only the free articles + | GIVEN ↘ Free Frieda has a free subscription + | WHEN ↘ Free Frieda logs in with her valid credentials + | THEN ↘ she sees a Free article TEST RESULT: 🟢 SUCCESS ⤷ FEATURE Subscribers see different articles based on their subscription level - ⤷ SCENARIO Subscriber with a paid subscription can access both free and paid articles - | GIVEN ↘ Paid Patty has a basic-level paid subscription - | WHEN ↘ Paid Patty logs in with her valid credentials - | THEN ↘ she sees a Free article and a Paid article + ⤷ SCENARIO Subscriber with a paid subscription can access both free and paid articles + | GIVEN ↘ Paid Patty has a basic-level paid subscription + | WHEN ↘ Paid Patty logs in with her valid credentials + | THEN ↘ she sees a Free article and a Paid article +``` + +### ✏️ Customizing the lexical elements of Gherkin + +The **GherXunit** allows you to customize the lexical elements of Gherkin, such as `Given`, `When`, `Then`, `And`, `Background`, `Scenario`, and `Feature`. +You can define your custom emojis or symbols to represent these elements. The following code snippet shows an example of a custom lexer for emojis: +```csharp +// Custom lexer for emojis +public record EmojiGherXunitLexer : IGherXunitLexer +{ + public (string Key, string Value)[] Given => [("Given", "\ud83d\ude10")]; + public (string Key, string Value)[] When => [("When", "\ud83c\udfac")]; + public (string Key, string Value)[] Then => [("Then", "\ud83d\ude4f")]; + public (string Key, string Value)[] And => [("And", "\ud83d\ude02")]; + public string Background => "\ud83d\udca4"; + public string Scenario => "\ud83e\udd52\ud83d\udcd5"; + public string Feature => "\ud83d\udcda"; +} +``` +The Gherkin provides two built-in lexers: `Lexers.PtBr` for Portuguese (🇵🇹🇧🇷) and `Lexers.EnUs` for English (🇺🇸). +You can also create your custom lexer by implementing the `IGherXunitLexer` interface. To use the custom lexer, +you need to pass it as a parameter when defining the test scenario. + +```csharp +[Feature("Subscribers see different articles based on their subscription level")] +public partial class LocalizationTest +{ + // Using Portuguese (🇵🇹🇧🇷) lexer + [Scenario("Inscrever-se para ver artigos gratuitos")] + async Task WhenFriedaLogs() => await this.ExecuteAscync( + refer: WhenFriedaLogsSteps, + lexer: Lexers.PtBr, + steps: """ + Dado Free Frieda possui uma assinatura gratuita + Quando Free Frieda faz login com suas credenciais válidas + Então ela vê um artigo gratuito + """); + + // Using custom emoji lexer + [Scenario("Subscriber with a paid subscription can access both free and paid articles")] + void WhenPattyLogs() => this.Execute( + refer: WhenPattyLogsSteps, + lexer: new EmojiGherXunitLexer(), + steps: """ + Given Paid Patty has a basic-level paid subscription + When Paid Patty logs in with her valid credentials + Then she sees a Free article and a Paid article + """); +} +``` +The result of running the test scenarios defined in the `LocalizationTest` class using the custom lexer would be similar to the following output: +```gherkindotnet +TEST RESULT: 🟢 SUCCESS +⤷ FUNCIONALIDADE Subscribers see different articles based on their subscription level + ⤷ CENARIO Inscrever-se para ver artigos gratuitos + | DADO ↘ Free Frieda possui uma assinatura gratuita + | QUANDO ↘ Free Frieda faz login com suas credenciais válidas + | ENTÃO ↘ ela vê um artigo gratuito + +TEST RESULT: 🟢 SUCCESS +⤷ 📚 Subscribers see different articles based on their subscription level + ⤷ 🥒📕 Subscriber with a paid subscription can access both free and paid articles + | 😐 ↘ Paid Patty has a basic-level paid subscription + | 🎬 ↘ Paid Patty logs in with her valid credentials + | 🙏 ↘ she sees a Free article and a Paid article ``` ### 🔎 Is GherXunit for You? diff --git a/README_PTBR.md b/README_PTBR.md index 0e2188b..e1f59fd 100644 --- a/README_PTBR.md +++ b/README_PTBR.md @@ -83,20 +83,87 @@ public partial class SubscriptionTest(ITestOutputHelper output): IGherXunit #### 📌 Exemplo de saída destacando os resultados dos testes: O resultado da execução dos cenários de teste definidos na classe `SubscriptionTest` seria semelhante à saída a seguir: -```shell +```gherkindotnet TEST RESULT: 🟢 SUCCESS ⤷ FEATURE Subscribers see different articles based on their subscription level - ⤷ SCENARIO Free subscribers see only the free articles - | GIVEN ↘ Free Frieda has a free subscription - | WHEN ↘ Free Frieda logs in with her valid credentials - | THEN ↘ she sees a Free article + ⤷ SCENARIO Free subscribers see only the free articles + | GIVEN ↘ Free Frieda has a free subscription + | WHEN ↘ Free Frieda logs in with her valid credentials + | THEN ↘ she sees a Free article TEST RESULT: 🟢 SUCCESS ⤷ FEATURE Subscribers see different articles based on their subscription level - ⤷ SCENARIO Subscriber with a paid subscription can access both free and paid articles - | GIVEN ↘ Paid Patty has a basic-level paid subscription - | WHEN ↘ Paid Patty logs in with her valid credentials - | THEN ↘ she sees a Free article and a Paid article + ⤷ SCENARIO Subscriber with a paid subscription can access both free and paid articles + | GIVEN ↘ Paid Patty has a basic-level paid subscription + | WHEN ↘ Paid Patty logs in with her valid credentials + | THEN ↘ she sees a Free article and a Paid article +``` + +### ✏️ Customizando a Sintaxe Gherkin + +O **GherXunit** permite personalizar os elementos lexicais do Gherkin, como `Given`, `When`, `Then`, `And`, `Background`, `Scenario` e `Feature`. +Você pode definir seus emojis ou símbolos personalizados para representar esses elementos. O trecho de código a seguir mostra um exemplo de um lexer personalizado para emojis: + +```csharp +// Custom lexer for emojis +public record EmojiGherXunitLexer : IGherXunitLexer +{ + public (string Key, string Value)[] Given => [("Given", "\ud83d\ude10")]; + public (string Key, string Value)[] When => [("When", "\ud83c\udfac")]; + public (string Key, string Value)[] Then => [("Then", "\ud83d\ude4f")]; + public (string Key, string Value)[] And => [("And", "\ud83d\ude02")]; + public string Background => "\ud83d\udca4"; + public string Scenario => "\ud83e\udd52\ud83d\udcd5"; + public string Feature => "\ud83d\udcda"; +} +``` +O **GherXunit** fornece dois lexers embutidos: `Lexers.PtBr` para Português (🇵🇹🇧🇷) e `Lexers.EnUs` para Inglês (🇺🇸). +Você também pode criar seu lexer personalizado implementando a interface `IGherXunitLexer`. Para usar o lexer personalizado, +você precisa passá-lo como parâmetro ao definir o cenário de teste. + +```csharp +[Feature("Subscribers see different articles based on their subscription level")] +public partial class LocalizationTest +{ + // Using Portuguese (🇵🇹🇧🇷) lexer + [Scenario("Inscrever-se para ver artigos gratuitos")] + async Task WhenFriedaLogs() => await this.ExecuteAscync( + refer: WhenFriedaLogsSteps, + lexer: Lexers.PtBr, + steps: """ + Dado Free Frieda possui uma assinatura gratuita + Quando Free Frieda faz login com suas credenciais válidas + Então ela vê um artigo gratuito + """); + + // Using custom emoji lexer + [Scenario("Subscriber with a paid subscription can access both free and paid articles")] + void WhenPattyLogs() => this.Execute( + refer: WhenPattyLogsSteps, + lexer: new EmojiGherXunitLexer(), + steps: """ + Given Paid Patty has a basic-level paid subscription + When Paid Patty logs in with her valid credentials + Then she sees a Free article and a Paid article + """); +} +``` + +O resultado da execução dos cenários de teste definidos na classe `LocalizationTest` usando o lexer personalizado seria semelhante à saída a seguir: +```gherkindotnet +TEST RESULT: 🟢 SUCCESS +⤷ FUNCIONALIDADE Subscribers see different articles based on their subscription level + ⤷ CENARIO Inscrever-se para ver artigos gratuitos + | DADO ↘ Free Frieda possui uma assinatura gratuita + | QUANDO ↘ Free Frieda faz login com suas credenciais válidas + | ENTÃO ↘ ela vê um artigo gratuito + +TEST RESULT: 🟢 SUCCESS +⤷ 📚 Subscribers see different articles based on their subscription level + ⤷ 🥒📕 Subscriber with a paid subscription can access both free and paid articles + | 😐 ↘ Paid Patty has a basic-level paid subscription + | 🎬 ↘ Paid Patty logs in with her valid credentials + | 🙏 ↘ she sees a Free article and a Paid article ``` ### 🔎 O GherXunit é para você? diff --git a/src/GherXunit.sln b/src/GherXunit.sln index 7e2d696..9397b42 100644 --- a/src/GherXunit.sln +++ b/src/GherXunit.sln @@ -2,12 +2,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{8B016D41-92A4-4ABC-9D36-D9A13D3B0E7D}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{FF816255-012E-4E4D-9C2B-E8AE0CE481A3}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GherXunit", "lib\GherXunit\GherXunit.csproj", "{121FDFEF-6F96-48F9-8DB3-ED4775C5E9F6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BddSample", "sample\BddSample\BddSample.csproj", "{4A38E3EA-C8A3-4F02-B7D5-BC63C302865B}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "base", "base", "{B014B043-DFB5-4827-8775-D4E5326130F0}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GherXunit.Core", "base\GherXunit.Core\GherXunit.Core.csproj", "{5477E886-B647-4A5D-A522-2978AF9F748D}" @@ -19,7 +15,6 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {121FDFEF-6F96-48F9-8DB3-ED4775C5E9F6} = {8B016D41-92A4-4ABC-9D36-D9A13D3B0E7D} - {4A38E3EA-C8A3-4F02-B7D5-BC63C302865B} = {FF816255-012E-4E4D-9C2B-E8AE0CE481A3} {5477E886-B647-4A5D-A522-2978AF9F748D} = {B014B043-DFB5-4827-8775-D4E5326130F0} EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution @@ -27,10 +22,6 @@ Global {121FDFEF-6F96-48F9-8DB3-ED4775C5E9F6}.Debug|Any CPU.Build.0 = Debug|Any CPU {121FDFEF-6F96-48F9-8DB3-ED4775C5E9F6}.Release|Any CPU.ActiveCfg = Release|Any CPU {121FDFEF-6F96-48F9-8DB3-ED4775C5E9F6}.Release|Any CPU.Build.0 = Release|Any CPU - {4A38E3EA-C8A3-4F02-B7D5-BC63C302865B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4A38E3EA-C8A3-4F02-B7D5-BC63C302865B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4A38E3EA-C8A3-4F02-B7D5-BC63C302865B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4A38E3EA-C8A3-4F02-B7D5-BC63C302865B}.Release|Any CPU.Build.0 = Release|Any CPU {5477E886-B647-4A5D-A522-2978AF9F748D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5477E886-B647-4A5D-A522-2978AF9F748D}.Debug|Any CPU.Build.0 = Debug|Any CPU {5477E886-B647-4A5D-A522-2978AF9F748D}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/src/base/GherXunit.Core/GherXunit.Core.csproj b/src/base/GherXunit.Core/GherXunit.Core.csproj index 1d2dd89..7c3f420 100644 --- a/src/base/GherXunit.Core/GherXunit.Core.csproj +++ b/src/base/GherXunit.Core/GherXunit.Core.csproj @@ -12,6 +12,8 @@ + + diff --git a/src/base/GherXunit.Core/Interfaces.cs b/src/base/GherXunit.Core/Interfaces.cs index 916e7fd..081e8d7 100644 --- a/src/base/GherXunit.Core/Interfaces.cs +++ b/src/base/GherXunit.Core/Interfaces.cs @@ -17,4 +17,16 @@ public interface IGherXunit : IGherXunit, IClassFixture where T : class; public interface IGherXunitOutputProvider { public ITestOutputHelper Output { get; } -} \ No newline at end of file +} + +//Lexer +public interface IGherXunitLexer +{ + public (string Key, string Value)[] Given { get; } + public (string Key, string Value)[] When { get; } + public (string Key, string Value)[] Then { get; } + public (string Key, string Value)[] And { get; } + public string Background { get; } + public string Scenario { get; } + public string Feature { get; } +} diff --git a/src/base/GherXunit.Core/Methods.cs b/src/base/GherXunit.Core/Methods.cs index c6b4c6b..c3e2cf8 100644 --- a/src/base/GherXunit.Core/Methods.cs +++ b/src/base/GherXunit.Core/Methods.cs @@ -7,54 +7,52 @@ namespace GherXunit.Annotations; public static class GherXunitSteps { - // Async methods - public static async Task BackgroundAsync(this IGherXunitStep scenario, Delegate refer, string steps) => await ExecuteAscync(scenario, refer.Method, steps, true); - public static async Task ExecuteAscync(this IGherXunitStep scenario, string steps) => await ExecuteAscync(scenario, null, steps, false); - public static async Task ExecuteAscync(this IGherXunitStep scenario, Delegate refer, string steps) => await ExecuteAscync(scenario, refer.Method, steps); - public static async Task ExecuteAscync(this IGherXunitStep scenario, Delegate refer, object[] param, string steps) => await ExecuteAscync(scenario, refer.Method, steps, false, param); - public static async Task NonExecutableAsync(this IGherXunitStep scenario, string? steps = null) => await ExecuteAscync(scenario, null, steps, false); - - // Sync methods - public static void Background(this IGherXunitStep scenario, Delegate refer, string steps) => Execute(scenario, refer.Method, steps, true); - public static void Execute(this IGherXunitStep scenario, string steps) => Execute(scenario, null, steps, false); - public static void Execute(this IGherXunitStep scenario, Delegate refer, string steps) => Execute(scenario, refer.Method, steps); - public static void Execute(this IGherXunitStep scenario, Delegate refer, object[] param, string steps) => Execute(scenario, refer.Method, steps, false, param); - public static void NonExecutable(this IGherXunitStep feature, string? steps) => Execute(feature, null, steps, false, []); + // Background methods + public static void Background(this IGherXunitStep scenario, Delegate refer, string steps, IGherXunitLexer? lexer = null) => InternalExecute(scenario, refer.Method, steps, true, lexer); + public static async Task BackgroundAsync(this IGherXunitStep scenario, Delegate refer, string steps, IGherXunitLexer? lexer = null) => await InternalExecuteAscync(scenario, refer.Method, steps, true, lexer); + + // Execute methods + public static void Execute(this IGherXunitStep scenario, string steps, Delegate? refer = null, object[]? param = null, IGherXunitLexer? lexer = null) => InternalExecute(scenario, refer?.Method, steps, false, lexer, param); + public static async Task ExecuteAscync(this IGherXunitStep scenario, string steps, Delegate? refer = null, object[]? param = null, IGherXunitLexer? lexer = null) => await InternalExecuteAscync(scenario, refer?.Method, steps, false, lexer, param); + + // Non Executable + public static void NonExecutable(this IGherXunitStep feature, string steps, IGherXunitLexer? lexer = null) => InternalExecute(feature, null, steps, false, lexer); + public static async Task NonExecutableAsync(this IGherXunitStep scenario, string steps, IGherXunitLexer? lexer = null) => await InternalExecuteAscync(scenario, null, steps, false, lexer); // Private methods - private static void Execute(this IGherXunitStep scenario, MethodInfo? method, string? steps, - bool isBackground = false, params object?[] param) + private static void InternalExecute(this IGherXunitStep scenario, MethodInfo? method, string? steps, + bool isBackground = false, IGherXunitLexer? lexer = null, params object[]? param) { try { method?.Invoke(scenario, param); - scenario.Write(method?.Name, steps, false, isBackground); + scenario.Write(method?.Name, steps, false, isBackground, lexer); } catch (Exception) { - scenario.Write(method?.Name, steps, true, isBackground); + scenario.Write(method?.Name, steps, true, isBackground, lexer); throw; } } - private static async Task ExecuteAscync(this IGherXunitStep scenario, MethodInfo? method, string? steps, - bool isBackground = false, params object?[] param) + private static async Task InternalExecuteAscync(this IGherXunitStep scenario, MethodInfo? method, string? steps, + bool isBackground = false, IGherXunitLexer? lexer = null, params object[]? param) { try { var task = method is null ? Task.CompletedTask : (Task)method.Invoke(scenario, param)!; await task; - scenario.Write(method?.Name, steps, false, isBackground); + scenario.Write(method?.Name, steps, false, isBackground, lexer); } catch (Exception) { - scenario.Write(method?.Name, steps, true, isBackground); + scenario.Write(method?.Name, steps, true, isBackground, lexer); throw; } } - private static void Write(this IGherXunitStep scenario, string? methodName, string? steps, bool isException = false, bool isBackground = false) + private static void Write(this IGherXunitStep scenario, string? methodName, string? steps, bool isException = false, bool isBackground = false, IGherXunitLexer? lexer = null) { if (steps is null) return; @@ -65,8 +63,8 @@ private static void Write(this IGherXunitStep scenario, string? methodName, stri var scenarioText = iTest is null ? $"{(isBackground ? "Background" : "Scenario")} {methodName}\r\n" : $"{(isBackground ? "Background" : "Scenario")} {iTest.DisplayName}\r\n"; - - var stepString = new StepStringHandler(); + + var stepString = new StringHandler(lexer ?? Lexers.EnUs); stepString.AppendLiteral($"{statusResult}{featuresText}{scenarioText}{steps}"); output?.WriteLine("."); diff --git a/src/base/GherXunit.Core/Backgrounds/BackgroundTest.Context.cs b/src/base/GherXunit.Core/Samples/Backgrounds/BackgroundTest.Context.cs similarity index 100% rename from src/base/GherXunit.Core/Backgrounds/BackgroundTest.Context.cs rename to src/base/GherXunit.Core/Samples/Backgrounds/BackgroundTest.Context.cs diff --git a/src/base/GherXunit.Core/Backgrounds/BackgroundTest.Steps.cs b/src/base/GherXunit.Core/Samples/Backgrounds/BackgroundTest.Steps.cs similarity index 100% rename from src/base/GherXunit.Core/Backgrounds/BackgroundTest.Steps.cs rename to src/base/GherXunit.Core/Samples/Backgrounds/BackgroundTest.Steps.cs diff --git a/src/base/GherXunit.Core/Backgrounds/BackgroundTest.cs b/src/base/GherXunit.Core/Samples/Backgrounds/BackgroundTest.cs similarity index 100% rename from src/base/GherXunit.Core/Backgrounds/BackgroundTest.cs rename to src/base/GherXunit.Core/Samples/Backgrounds/BackgroundTest.cs diff --git a/src/base/GherXunit.Core/Features/SubscriptionTest.Steps.cs b/src/base/GherXunit.Core/Samples/Features/SubscriptionTest.Steps.cs similarity index 100% rename from src/base/GherXunit.Core/Features/SubscriptionTest.Steps.cs rename to src/base/GherXunit.Core/Samples/Features/SubscriptionTest.Steps.cs diff --git a/src/base/GherXunit.Core/Features/SubscriptionTest.cs b/src/base/GherXunit.Core/Samples/Features/SubscriptionTest.cs similarity index 100% rename from src/base/GherXunit.Core/Features/SubscriptionTest.cs rename to src/base/GherXunit.Core/Samples/Features/SubscriptionTest.cs diff --git a/src/sample/BddSample/Samples/Features/SubscriptionTest.Steps.cs b/src/base/GherXunit.Core/Samples/Localization/LocalizationTest.Steps.cs similarity index 71% rename from src/sample/BddSample/Samples/Features/SubscriptionTest.Steps.cs rename to src/base/GherXunit.Core/Samples/Localization/LocalizationTest.Steps.cs index 147f7d1..b4f13ea 100644 --- a/src/sample/BddSample/Samples/Features/SubscriptionTest.Steps.cs +++ b/src/base/GherXunit.Core/Samples/Localization/LocalizationTest.Steps.cs @@ -1,9 +1,9 @@ using GherXunit.Annotations; using Xunit.Abstractions; -namespace BddTests.Samples.Features; +namespace BddTests.Samples.Localization; -public partial class SubscriptionTest(ITestOutputHelper output): IGherXunit +public partial class LocalizationTest(ITestOutputHelper output): IGherXunit { public ITestOutputHelper Output { get; } = output; private void WhenPattyLogsSteps() => Assert.True(true); diff --git a/src/base/GherXunit.Core/Samples/Localization/LocalizationTest.cs b/src/base/GherXunit.Core/Samples/Localization/LocalizationTest.cs new file mode 100644 index 0000000..9863c04 --- /dev/null +++ b/src/base/GherXunit.Core/Samples/Localization/LocalizationTest.cs @@ -0,0 +1,39 @@ +using GherXunit.Annotations; + +namespace BddTests.Samples.Localization; + +[Feature("Subscribers see different articles based on their subscription level")] +public partial class LocalizationTest +{ + [Scenario("Inscrever-se para ver artigos gratuitos")] + async Task WhenFriedaLogs() => await this.ExecuteAscync( + refer: WhenFriedaLogsSteps, + lexer: Lexers.PtBr, + steps: """ + Dado Free Frieda possui uma assinatura gratuita + Quando Free Frieda faz login com suas credenciais válidas + Então ela vê um artigo gratuito + """); + + [Scenario("Subscriber with a paid subscription can access both free and paid articles")] + void WhenPattyLogs() => this.Execute( + refer: WhenPattyLogsSteps, + lexer: new EmojiGherXunitLexer(), + steps: """ + Given Paid Patty has a basic-level paid subscription + When Paid Patty logs in with her valid credentials + Then she sees a Free article and a Paid article + """); +} + +// Custom lexer for emojis +public record EmojiGherXunitLexer : IGherXunitLexer +{ + public (string Key, string Value)[] Given => [("Given", "\ud83d\ude10")]; + public (string Key, string Value)[] When => [("When", "\ud83c\udfac")]; + public (string Key, string Value)[] Then => [("Then", "\ud83d\ude4f")]; + public (string Key, string Value)[] And => [("And", "\ud83d\ude02")]; + public string Background => "\ud83d\udca4"; + public string Scenario => "\ud83e\udd52\ud83d\udcd5"; + public string Feature => "\ud83d\udcda"; +} \ No newline at end of file diff --git a/src/sample/BddSample/Samples/Outline/ScenarioOutlineTest.Steps.cs b/src/base/GherXunit.Core/Samples/Outline/ScenarioOutlineTest.Steps.cs similarity index 100% rename from src/sample/BddSample/Samples/Outline/ScenarioOutlineTest.Steps.cs rename to src/base/GherXunit.Core/Samples/Outline/ScenarioOutlineTest.Steps.cs diff --git a/src/sample/BddSample/Samples/Outline/ScenarioOutlineTest.cs b/src/base/GherXunit.Core/Samples/Outline/ScenarioOutlineTest.cs similarity index 100% rename from src/sample/BddSample/Samples/Outline/ScenarioOutlineTest.cs rename to src/base/GherXunit.Core/Samples/Outline/ScenarioOutlineTest.cs diff --git a/src/sample/BddSample/Samples/Refactoring/ExemploA/ProcessamentoServiceTests.cs b/src/base/GherXunit.Core/Samples/Refactoring/ExemploA/ProcessamentoServiceTests.cs similarity index 70% rename from src/sample/BddSample/Samples/Refactoring/ExemploA/ProcessamentoServiceTests.cs rename to src/base/GherXunit.Core/Samples/Refactoring/ExemploA/ProcessamentoServiceTests.cs index 47907d8..bbfdc3f 100644 --- a/src/sample/BddSample/Samples/Refactoring/ExemploA/ProcessamentoServiceTests.cs +++ b/src/base/GherXunit.Core/Samples/Refactoring/ExemploA/ProcessamentoServiceTests.cs @@ -28,13 +28,13 @@ And a confirmation message is logged public partial class DataProcessingServiceTests(ITestOutputHelper output) : IGherXunit { - private Mock _apiClientMock; - private Mock _repositoryMock; - private Mock _queueServiceMock; - private Mock> _loggerMock; - private DataProcessingService _dataProcessingService; + private Mock? _apiClientMock; + private Mock? _repositoryMock; + private Mock? _queueServiceMock; + private Mock>? _loggerMock; + private DataProcessingService? _dataProcessingService; - public ITestOutputHelper? Output { get; } = output; + public ITestOutputHelper Output { get; } = output; private Task Setup() { @@ -57,30 +57,30 @@ private async Task ProcessData_ShouldProcessSuccessfully() // Arrange var data = new Data { Value = "Test" }; - _apiClientMock.Setup(api => api.FetchDataAsync()) + _apiClientMock?.Setup(api => api.FetchDataAsync()) .ReturnsAsync(data); - _repositoryMock.Setup(repo => repo.SaveDataAsync(data)) + _repositoryMock?.Setup(repo => repo.SaveDataAsync(data)) .Returns(Task.CompletedTask); - _queueServiceMock.Setup(queue => queue.PublishMessage(data.Value)) + _queueServiceMock?.Setup(queue => queue.PublishMessage(data.Value)) .Returns(Task.CompletedTask); // Act - await _dataProcessingService.ProcessData(); + await _dataProcessingService?.ProcessData()!; // Assert - _apiClientMock.Verify(api => api.FetchDataAsync(), Times.Once); - _repositoryMock.Verify(repo => repo.SaveDataAsync(data), Times.Once); - _queueServiceMock.Verify(queue => queue.PublishMessage(data.Value), Times.Once); + _apiClientMock?.Verify(api => api.FetchDataAsync(), Times.Once); + _repositoryMock?.Verify(repo => repo.SaveDataAsync(data), Times.Once); + _queueServiceMock?.Verify(queue => queue.PublishMessage(data.Value), Times.Once); - _loggerMock.Verify( + _loggerMock?.Verify( log => log.Log( LogLevel.Information, It.IsAny(), - It.Is((v, t) => v.ToString().Contains("Processing completed")), + It.Is((v, t) => v.ToString()!.Contains("Processing completed")), null, - It.IsAny>() + It.IsAny>()! ), Times.Once); } @@ -88,22 +88,22 @@ private async Task ProcessData_ShouldProcessSuccessfully() public async Task ProcessData_ShouldThrowExceptionWhenApiFails() { // Arrange - _apiClientMock.Setup(api => api.FetchDataAsync()) + _apiClientMock?.Setup(api => api.FetchDataAsync()) .ThrowsAsync(new HttpRequestException("API failure")); // Act & Assert - await Assert.ThrowsAsync(() => _dataProcessingService.ProcessData()); + await Assert.ThrowsAsync(() => _dataProcessingService!.ProcessData()); - _repositoryMock.Verify(repo => repo.SaveDataAsync(It.IsAny()), Times.Never); - _queueServiceMock.Verify(queue => queue.PublishMessage(It.IsAny()), Times.Never); + _repositoryMock?.Verify(repo => repo.SaveDataAsync(It.IsAny()), Times.Never); + _queueServiceMock?.Verify(queue => queue.PublishMessage(It.IsAny()), Times.Never); - _loggerMock.Verify( + _loggerMock?.Verify( log => log.Log( LogLevel.Error, It.IsAny(), - It.Is((v, t) => v.ToString().Contains("Error fetching data from API")), + It.Is((v, t) => v.ToString()!.Contains("Error fetching data from API")), It.IsAny(), - It.IsAny>() + It.IsAny>()! ), Times.Once); } @@ -113,24 +113,24 @@ public async Task ProcessData_ShouldThrowExceptionWhenDatabaseFails() // Arrange var data = new Data { Value = "Test" }; - _apiClientMock.Setup(api => api.FetchDataAsync()) + _apiClientMock?.Setup(api => api.FetchDataAsync()) .ReturnsAsync(data); - _repositoryMock.Setup(repo => repo.SaveDataAsync(data)) + _repositoryMock?.Setup(repo => repo.SaveDataAsync(data)) .ThrowsAsync(new Exception("Database failure")); // Act & Assert - await Assert.ThrowsAsync(() => _dataProcessingService.ProcessData()); + await Assert.ThrowsAsync(() => _dataProcessingService!.ProcessData()); - _queueServiceMock.Verify(queue => queue.PublishMessage(It.IsAny()), Times.Never); + _queueServiceMock?.Verify(queue => queue.PublishMessage(It.IsAny()), Times.Never); - _loggerMock.Verify( + _loggerMock?.Verify( log => log.Log( LogLevel.Error, It.IsAny(), - It.Is((v, t) => v.ToString().Contains("Error saving data to the database")), + It.Is((v, t) => v.ToString()!.Contains("Error saving data to the database")), It.IsAny(), - It.IsAny>() + It.IsAny>()! ), Times.Once); } @@ -140,25 +140,25 @@ public async Task ProcessData_ShouldThrowExceptionWhenQueueFails() // Arrange var data = new Data { Value = "Test" }; - _apiClientMock.Setup(api => api.FetchDataAsync()) + _apiClientMock?.Setup(api => api.FetchDataAsync()) .ReturnsAsync(data); - _repositoryMock.Setup(repo => repo.SaveDataAsync(data)) + _repositoryMock?.Setup(repo => repo.SaveDataAsync(data)) .Returns(Task.CompletedTask); - _queueServiceMock.Setup(queue => queue.PublishMessage(data.Value)) + _queueServiceMock?.Setup(queue => queue.PublishMessage(data.Value)) .ThrowsAsync(new Exception("Queue failure")); // Act & Assert - await Assert.ThrowsAsync(() => _dataProcessingService.ProcessData()); + await Assert.ThrowsAsync(() => _dataProcessingService!.ProcessData()); - _loggerMock.Verify( + _loggerMock?.Verify( log => log.Log( LogLevel.Error, It.IsAny(), - It.Is((v, t) => v.ToString().Contains("Error publishing message to the queue")), + It.Is((v, t) => v.ToString()!.Contains("Error publishing message to the queue")), It.IsAny(), - It.IsAny>() + It.IsAny>()! ), Times.Once); } diff --git a/src/sample/BddSample/Samples/Rules/RuleTest.Steps.cs b/src/base/GherXunit.Core/Samples/Rules/RuleTest.Steps.cs similarity index 100% rename from src/sample/BddSample/Samples/Rules/RuleTest.Steps.cs rename to src/base/GherXunit.Core/Samples/Rules/RuleTest.Steps.cs diff --git a/src/sample/BddSample/Samples/Rules/RuleTest.cs b/src/base/GherXunit.Core/Samples/Rules/RuleTest.cs similarity index 100% rename from src/sample/BddSample/Samples/Rules/RuleTest.cs rename to src/base/GherXunit.Core/Samples/Rules/RuleTest.cs diff --git a/src/base/GherXunit.Core/StepStringHandler.cs b/src/base/GherXunit.Core/StepStringHandler.cs deleted file mode 100644 index 45e2c02..0000000 --- a/src/base/GherXunit.Core/StepStringHandler.cs +++ /dev/null @@ -1,26 +0,0 @@ -#nullable enable -using System.Runtime.CompilerServices; - -namespace GherXunit.Annotations; - -[InterpolatedStringHandler] -public ref struct StepStringHandler() -{ - private string _message = ""; - - public void AppendLiteral(string s) => _message += HighlightKeyword(s); - public void AppendFormatted(T value) => _message += value?.ToString(); - public override string ToString() => _message; - - private string HighlightKeyword(string input) - { - return input - .Replace("Feature", $"{"\u2937 FEATURE",8}") - .Replace("Scenario", $"{"\u2937 SCENARIO",13}") - .Replace("Background", $"{"\u2937 BACKGROUND",15}") - .Replace("Given", $"{"|",7} {"GIVEN",5} \u2198") - .Replace("When", $"{"|",7} {"WHEN" ,5} \u2198") - .Replace("Then", $"{"|",7} {"THEN" ,5} \u2198") - .Replace("And", $"{"|",7} {"AND" ,5} \u2198"); - } -} \ No newline at end of file diff --git a/src/base/GherXunit.Core/StringHandler.cs b/src/base/GherXunit.Core/StringHandler.cs new file mode 100644 index 0000000..b1ed3ea --- /dev/null +++ b/src/base/GherXunit.Core/StringHandler.cs @@ -0,0 +1,64 @@ +#nullable enable +using System.Runtime.CompilerServices; + +namespace GherXunit.Annotations; + +[InterpolatedStringHandler] +public class StringHandler(IGherXunitLexer lexer) +{ + private string _message = ""; + + public void AppendLiteral(string s) => _message += HighlightKeyword(s); + public override string ToString() => _message; + + private string HighlightKeyword(string input) + { + int[] words = + [ + lexer.Given.Max(x => x.Value.Length), + lexer.When.Max(x => x.Value.Length), + lexer.Then.Max(x => x.Value.Length), + lexer.And.Max(x => x.Value.Length) + ]; + + var max = words.Max() + 1; + + lexer.Given.ToList().ForEach(x => input = input.Replace($"\n{x.Key}", $"\n{"|",5}{x.Value.PadLeft(max)} \u2198")); + lexer.When.ToList().ForEach(x => input = input.Replace($"\n{x.Key}", $"\n{"|",5}{x.Value.PadLeft(max)} \u2198")); + lexer.Then.ToList().ForEach(x => input = input.Replace($"\n{x.Key}", $"\n{"|",5}{x.Value.PadLeft(max)} \u2198")); + lexer.And.ToList().ForEach(x => input = input.Replace($"\n{x.Key}", $"\n{"|",5}{x.Value.PadLeft(max)} \u2198")); + + return input + .Replace(nameof(lexer.Feature), $"\u2937 {lexer.Feature}") + .Replace(nameof(lexer.Scenario), $"{"\u2937",3} {lexer.Scenario}") + .Replace(nameof(lexer.Background), $"{"\u2937",3} {lexer.Background}"); + } +} + +public static class Lexers +{ + public static IGherXunitLexer EnUs => new DefaultGherXunitLexer(); + public static IGherXunitLexer PtBr => new PtBrGherXunitLexer(); +} + +public record DefaultGherXunitLexer : IGherXunitLexer +{ + public (string Key, string Value)[] Given => [("Given", "GIVEN")]; + public (string Key, string Value)[] When => [("When", "WHEN")]; + public (string Key, string Value)[] Then => [("Then", "THEN")]; + public (string Key, string Value)[] And => [("And", "AND")]; + public string Background => "BACKGROUND"; + public string Scenario => "SCENARIO"; + public string Feature => "FEATURE"; +} + +public record PtBrGherXunitLexer : IGherXunitLexer +{ + public (string Key, string Value)[] Given => [("Dado", "DADO"), ("Dada", "DADA"), ("Dados", "DADOS"), ("Dadas", "DADAS")]; + public (string Key, string Value)[] When => [("Quando", "QUANDO")]; + public (string Key, string Value)[] Then => [("Então", "ENTÃO"), ("Entao", "ENTAO")]; + public (string Key, string Value)[] And => [("E", "E")]; + public string Background => "CONTEXTO"; + public string Scenario => "CENARIO"; + public string Feature => "FUNCIONALIDADE"; +} \ No newline at end of file diff --git a/src/lib/GherXunit/GherXunit.Content.Interfaces.cs b/src/lib/GherXunit/GherXunit.Content.Interfaces.cs index 068568f..f61b8bb 100644 --- a/src/lib/GherXunit/GherXunit.Content.Interfaces.cs +++ b/src/lib/GherXunit/GherXunit.Content.Interfaces.cs @@ -28,5 +28,17 @@ public interface IGherXunitOutputProvider { public ITestOutputHelper Output { get; } } + + //Lexer + public interface IGherXunitLexer + { + public (string Key, string Value)[] Given { get; } + public (string Key, string Value)[] When { get; } + public (string Key, string Value)[] Then { get; } + public (string Key, string Value)[] And { get; } + public string Background { get; } + public string Scenario { get; } + public string Feature { get; } + } """; } \ No newline at end of file diff --git a/src/lib/GherXunit/GherXunit.Content.Methods.cs b/src/lib/GherXunit/GherXunit.Content.Methods.cs index 2f95d73..bce9838 100644 --- a/src/lib/GherXunit/GherXunit.Content.Methods.cs +++ b/src/lib/GherXunit/GherXunit.Content.Methods.cs @@ -12,59 +12,57 @@ public struct GherXunitMethods using System.Reflection; using Xunit.Abstractions; using Xunit.Sdk; - + namespace GherXunit.Annotations; - + public static class GherXunitSteps { - // Async methods - public static async Task BackgroundAsync(this IGherXunitStep scenario, Delegate refer, string steps) => await ExecuteAscync(scenario, refer.Method, steps, true); - public static async Task ExecuteAscync(this IGherXunitStep scenario, string steps) => await ExecuteAscync(scenario, null, steps, false); - public static async Task ExecuteAscync(this IGherXunitStep scenario, Delegate refer, string steps) => await ExecuteAscync(scenario, refer.Method, steps); - public static async Task ExecuteAscync(this IGherXunitStep scenario, Delegate refer, object[] param, string steps) => await ExecuteAscync(scenario, refer.Method, steps, false, param); - public static async Task NonExecutableAsync(this IGherXunitStep scenario, string? steps = null) => await ExecuteAscync(scenario, null, steps, false); - - // Sync methods - public static void Background(this IGherXunitStep scenario, Delegate refer, string steps) => Execute(scenario, refer.Method, steps, true); - public static void Execute(this IGherXunitStep scenario, string steps) => Execute(scenario, null, steps, false); - public static void Execute(this IGherXunitStep scenario, Delegate refer, string steps) => Execute(scenario, refer.Method, steps); - public static void Execute(this IGherXunitStep scenario, Delegate refer, object[] param, string steps) => Execute(scenario, refer.Method, steps, false, param); - public static void NonExecutable(this IGherXunitStep feature, string? steps) => Execute(feature, null, steps, false, []); + // Background methods + public static void Background(this IGherXunitStep scenario, Delegate refer, string steps, IGherXunitLexer? lexer = null) => InternalExecute(scenario, refer.Method, steps, true, lexer); + public static async Task BackgroundAsync(this IGherXunitStep scenario, Delegate refer, string steps, IGherXunitLexer? lexer = null) => await InternalExecuteAscync(scenario, refer.Method, steps, true, lexer); + + // Execute methods + public static void Execute(this IGherXunitStep scenario, string steps, Delegate? refer = null, object[]? param = null, IGherXunitLexer? lexer = null) => InternalExecute(scenario, refer?.Method, steps, false, lexer, param); + public static async Task ExecuteAscync(this IGherXunitStep scenario, string steps, Delegate? refer = null, object[]? param = null, IGherXunitLexer? lexer = null) => await InternalExecuteAscync(scenario, refer?.Method, steps, false, lexer, param); + + // Non Executable + public static void NonExecutable(this IGherXunitStep feature, string steps, IGherXunitLexer? lexer = null) => InternalExecute(feature, null, steps, false, lexer); + public static async Task NonExecutableAsync(this IGherXunitStep scenario, string steps, IGherXunitLexer? lexer = null) => await InternalExecuteAscync(scenario, null, steps, false, lexer); // Private methods - private static void Execute(this IGherXunitStep scenario, MethodInfo? method, string? steps, - bool isBackground = false, params object?[] param) + private static void InternalExecute(this IGherXunitStep scenario, MethodInfo? method, string? steps, + bool isBackground = false, IGherXunitLexer? lexer = null, params object[]? param) { try { method?.Invoke(scenario, param); - scenario.Write(method?.Name, steps, false, isBackground); + scenario.Write(method?.Name, steps, false, isBackground, lexer); } catch (Exception) { - scenario.Write(method?.Name, steps, true, isBackground); + scenario.Write(method?.Name, steps, true, isBackground, lexer); throw; } } - private static async Task ExecuteAscync(this IGherXunitStep scenario, MethodInfo? method, string? steps, - bool isBackground = false, params object?[] param) + private static async Task InternalExecuteAscync(this IGherXunitStep scenario, MethodInfo? method, string? steps, + bool isBackground = false, IGherXunitLexer? lexer = null, params object[]? param) { try { var task = method is null ? Task.CompletedTask : (Task)method.Invoke(scenario, param)!; await task; - scenario.Write(method?.Name, steps, false, isBackground); + scenario.Write(method?.Name, steps, false, isBackground, lexer); } catch (Exception) { - scenario.Write(method?.Name, steps, true, isBackground); + scenario.Write(method?.Name, steps, true, isBackground, lexer); throw; } } - private static void Write(this IGherXunitStep scenario, string? methodName, string? steps, bool isException = false, bool isBackground = false) + private static void Write(this IGherXunitStep scenario, string? methodName, string? steps, bool isException = false, bool isBackground = false, IGherXunitLexer? lexer = null) { if (steps is null) return; @@ -75,8 +73,8 @@ private static void Write(this IGherXunitStep scenario, string? methodName, stri var scenarioText = iTest is null ? $"{(isBackground ? "Background" : "Scenario")} {methodName}\r\n" : $"{(isBackground ? "Background" : "Scenario")} {iTest.DisplayName}\r\n"; - - var stepString = new StepStringHandler(); + + var stepString = new StringHandler(lexer ?? Lexers.Default); stepString.AppendLiteral($"{statusResult}{featuresText}{scenarioText}{steps}"); output?.WriteLine("."); diff --git a/src/lib/GherXunit/GherXunit.Content.StringHandler.cs b/src/lib/GherXunit/GherXunit.Content.StringHandler.cs index e67efe3..99cf2a0 100644 --- a/src/lib/GherXunit/GherXunit.Content.StringHandler.cs +++ b/src/lib/GherXunit/GherXunit.Content.StringHandler.cs @@ -10,25 +10,63 @@ public struct GherXunitStepStringHandler namespace GherXunit.Annotations; [InterpolatedStringHandler] - public ref struct StepStringHandler() + public class StringHandler(IGherXunitLexer lexer) { private string _message = ""; public void AppendLiteral(string s) => _message += HighlightKeyword(s); - public void AppendFormatted(T value) => _message += value?.ToString(); public override string ToString() => _message; private string HighlightKeyword(string input) { + int[] words = + [ + lexer.Given.Max(x => x.Value.Length), + lexer.When.Max(x => x.Value.Length), + lexer.Then.Max(x => x.Value.Length), + lexer.And.Max(x => x.Value.Length) + ]; + + var max = words.Max() + 1; + + lexer.Given.ToList().ForEach(x => input = input.Replace($"\n{x.Key}", $"\n{"|",5}{x.Value.PadLeft(max)} \u2198")); + lexer.When.ToList().ForEach(x => input = input.Replace($"\n{x.Key}", $"\n{"|",5}{x.Value.PadLeft(max)} \u2198")); + lexer.Then.ToList().ForEach(x => input = input.Replace($"\n{x.Key}", $"\n{"|",5}{x.Value.PadLeft(max)} \u2198")); + lexer.And.ToList().ForEach(x => input = input.Replace($"\n{x.Key}", $"\n{"|",5}{x.Value.PadLeft(max)} \u2198")); + return input - .Replace("Feature", $"{"\u2937 FEATURE",8}") - .Replace("Scenario", $"{"\u2937 SCENARIO",13}") - .Replace("Background", $"{"\u2937 BACKGROUND",15}") - .Replace("Given", $"{"|",7} {"GIVEN",5} \u2198") - .Replace("When", $"{"|",7} {"WHEN" ,5} \u2198") - .Replace("Then", $"{"|",7} {"THEN" ,5} \u2198") - .Replace("And", $"{"|",7} {"AND" ,5} \u2198"); + .Replace(nameof(lexer.Feature), $"\u2937 {lexer.Feature}") + .Replace(nameof(lexer.Scenario), $"{"\u2937",3} {lexer.Scenario}") + .Replace(nameof(lexer.Background), $"{"\u2937",3} {lexer.Background}"); } } + + public static class Lexers + { + public static IGherXunitLexer Default => new DefaultGherXunitLexer(); + public static IGherXunitLexer PtBr => new PtBrGherXunitLexer(); + } + + public record DefaultGherXunitLexer : IGherXunitLexer + { + public (string Key, string Value)[] Given => [("Given", "GIVEN")]; + public (string Key, string Value)[] When => [("When", "WHEN")]; + public (string Key, string Value)[] Then => [("Then", "THEN")]; + public (string Key, string Value)[] And => [("And", "AND")]; + public string Background => "BACKGROUND"; + public string Scenario => "SCENARIO"; + public string Feature => "FEATURE"; + } + + public record PtBrGherXunitLexer : IGherXunitLexer + { + public (string Key, string Value)[] Given => [("Dado", "DADO"), ("Dada", "DADA"), ("Dados", "DADOS"), ("Dadas", "DADAS")]; + public (string Key, string Value)[] When => [("Quando", "QUANDO")]; + public (string Key, string Value)[] Then => [("Então", "ENTÃO"), ("Entao", "ENTAO")]; + public (string Key, string Value)[] And => [("E", "E")]; + public string Background => "CONTEXTO"; + public string Scenario => "CENARIO"; + public string Feature => "FUNCIONALIDADE"; + } """; } \ No newline at end of file diff --git a/src/sample/BddSample/BddSample.csproj b/src/sample/BddSample/BddSample.csproj deleted file mode 100644 index a741bc2..0000000 --- a/src/sample/BddSample/BddSample.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - - net9.0 - enable - enable - false - BddTests - - - - - - - - - - - - - - - - - - - diff --git a/src/sample/BddSample/Samples/Backgrounds/BackgroundTest.Context.cs b/src/sample/BddSample/Samples/Backgrounds/BackgroundTest.Context.cs deleted file mode 100644 index b060502..0000000 --- a/src/sample/BddSample/Samples/Backgrounds/BackgroundTest.Context.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace BddTests.Samples.Backgrounds; - -public class BackgroundContext -{ - public string ContextId { get; } = Guid.NewGuid().ToString(); - public Dictionary OwnersAndBlogs { get; } = new(); - - public BackgroundContext() - { - OwnersAndBlogs.Add("Greg", "Greg's anti-tax rants"); - OwnersAndBlogs.Add("Dr. Bill", "Expensive Therapy"); - } -} \ No newline at end of file diff --git a/src/sample/BddSample/Samples/Backgrounds/BackgroundTest.Steps.cs b/src/sample/BddSample/Samples/Backgrounds/BackgroundTest.Steps.cs deleted file mode 100644 index 3760b31..0000000 --- a/src/sample/BddSample/Samples/Backgrounds/BackgroundTest.Steps.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Runtime.CompilerServices; -using GherXunit.Annotations; -using Xunit.Abstractions; - -namespace BddTests.Samples.Backgrounds; - -public partial class BackgroundTest(ITestOutputHelper output) : IGherXunit, IAsyncLifetime -{ - public ITestOutputHelper Output { get; } = output; - private BackgroundContext? Context { get; set; } - - private Task Setup() - { - Context = new BackgroundContext(); - return Task.CompletedTask; - } - - private async Task DrBillPostToBlogStep() - { - var result = await PostToBlog("Dr. Bill", "Expensive Therapy"); - Assert.Equal("Your article was published.", result); - } - - private async Task DrBillPostToBlogFailStep() - { - var result = await PostToBlog("Dr. Bill", "Greg's anti-tax rants"); - Assert.Equal("Hey! That's not your blog!", result); - } - - private async Task GregPostToBlogStep() - { - var result = await PostToBlog("Greg", "Greg's anti-tax rants"); - Assert.Equal("Your article was published.", result); - } - - private async Task PostToBlog(string owner, string blog) - { - if (!Context!.OwnersAndBlogs.TryGetValue(owner, out string? value) || value != blog) - return "Hey! That's not your blog!"; - - await Task.Yield(); - return "Your article was published."; - } - - public Task DisposeAsync() - { - return Task.CompletedTask; - } -} \ No newline at end of file diff --git a/src/sample/BddSample/Samples/Backgrounds/BackgroundTest.cs b/src/sample/BddSample/Samples/Backgrounds/BackgroundTest.cs deleted file mode 100644 index d0b13d0..0000000 --- a/src/sample/BddSample/Samples/Backgrounds/BackgroundTest.cs +++ /dev/null @@ -1,45 +0,0 @@ -using GherXunit.Annotations; -using Xunit.Abstractions; - -namespace BddTests.Samples.Backgrounds; - -[Feature("Multiple site support Only blog owners can post to a blog, except administrators, who can post to all blogs.")] -public partial class BackgroundTest -{ - [Background] - public async Task InitializeAsync() => await this.BackgroundAsync( - refer: Setup, - steps: """ - Given a global administrator named <<"Greg">> - And a blog named <<"Greg's anti-tax rants">> - And a customer named <<"Dr. Bill">> - And a blog named <<"Expensive Therapy">> owned by <<"Dr. Bill">> - """); - - [Scenario("Dr. Bill posts to his own blog")] - public async Task DrBillPostToBlog() => await this.ExecuteAscync( - refer: DrBillPostToBlogStep, - steps: """ - Given I am logged in as Dr. Bill - When I try to post to "Expensive Therapy" - Then I should see "Your article was published." - """); - - [Scenario("Dr. Bill tries to post to somebody else's blog, and fails")] - public async Task DrBillPostToBlogFail() => await this.ExecuteAscync( - refer: DrBillPostToBlogFailStep, - steps: """ - Given I am logged in as Dr. Bill - When I try to post to "Greg's anti-tax rants" - Then I should see "Hey! That's not your blog!" - """); - - [Scenario("Greg posts to a client's blog")] - public async Task GregPostToBlog() => await this.ExecuteAscync( - refer: GregPostToBlogStep, - steps: """ - Given I am logged in as Greg - When I try to post to "Expensive Therapy" - Then I should see "Your article was published." - """); -} \ No newline at end of file diff --git a/src/sample/BddSample/Samples/Features/SubscriptionTest.cs b/src/sample/BddSample/Samples/Features/SubscriptionTest.cs deleted file mode 100644 index 452cc1a..0000000 --- a/src/sample/BddSample/Samples/Features/SubscriptionTest.cs +++ /dev/null @@ -1,25 +0,0 @@ -using GherXunit.Annotations; - -namespace BddTests.Samples.Features; - -[Feature("Subscribers see different articles based on their subscription level")] -public partial class SubscriptionTest -{ - [Scenario("Free subscribers see only the free articles")] - async Task WhenFriedaLogs() => await this.ExecuteAscync( - refer: WhenFriedaLogsSteps, - steps: """ - Given Free Frieda has a free subscription - When Free Frieda logs in with her valid credentials - Then she sees a Free article - """); - - [Scenario("Subscriber with a paid subscription can access both free and paid articles")] - void WhenPattyLogs() => this.Execute( - refer: WhenPattyLogsSteps, - steps: """ - Given Paid Patty has a basic-level paid subscription - When Paid Patty logs in with her valid credentials - Then she sees a Free article and a Paid article - """); -} \ No newline at end of file