Skip to content

Commit a28dc70

Browse files
committed
ReqnrollFormatters.CustomizedHtml: First version
1 parent aa84de4 commit a28dc70

File tree

10 files changed

+341
-0
lines changed

10 files changed

+341
-0
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
using System.Text.RegularExpressions;
2+
using Cucumber.HtmlFormatter;
3+
using Io.Cucumber.Messages.Types;
4+
using Reqnroll.Formatters.Configuration;
5+
using Reqnroll.Formatters.Html;
6+
using Reqnroll.Formatters.RuntimeSupport;
7+
using Reqnroll.Utils;
8+
9+
namespace ReqnrollFormatters.CustomizedHtml;
10+
11+
public class CustomizedHtmlFormatter(IFormattersConfigurationProvider configurationProvider, IFormatterLog logger, IFileSystem fileSystem)
12+
: HtmlFormatter(configurationProvider, logger, fileSystem, "customizedHtml")
13+
{
14+
public class CustomizedMessagesToHtmlWriter : MessagesToHtmlWriter
15+
{
16+
public CustomizedMessagesToHtmlWriter(Stream stream, Func<StreamWriter, Envelope, Task> asyncStreamSerializer) : base(stream, asyncStreamSerializer)
17+
{
18+
}
19+
20+
public CustomizedMessagesToHtmlWriter(StreamWriter writer, Func<StreamWriter, Envelope, Task> asyncStreamSerializer) : base(writer, asyncStreamSerializer)
21+
{
22+
}
23+
24+
protected override string GetResource(string name)
25+
{
26+
string originalResource = base.GetResource(name);
27+
switch (name)
28+
{
29+
case "main.css":
30+
return originalResource + "\n" +
31+
"""
32+
/* Custom theme, see https://github.com/cucumber/react-components?tab=readme-ov-file#theming */
33+
.dark-theme {
34+
background-color: #1d1d26;
35+
color: #c9c9d1;
36+
--cucumber-anchor-color: #4caaee;
37+
--cucumber-keyword-color: #d89077;
38+
--cucumber-parameter-color: #4caaee;
39+
--cucumber-tag-color: #85a658;
40+
--cucumber-docstring-color: #66a565;
41+
--cucumber-error-background-color: #cf6679;
42+
--cucumber-error-text-color: #222;
43+
--cucumber-code-background-color: #282a36;
44+
--cucumber-code-text-color: #f8f8f2;
45+
--cucumber-panel-background-color: #282a36;
46+
--cucumber-panel-accent-color: #313442;
47+
--cucumber-panel-text-color: #f8f8f2;
48+
}
49+
""";
50+
case "index.mustache.html":
51+
{
52+
return originalResource.Replace("<div id=\"content\">", "<div id='content' class='dark-theme'>");
53+
}
54+
case "main.js":
55+
var globalVarsMatch = Regex.Match(originalResource, @"\.render\((?<reactObj>[\w\.]+)\.createElement\((?<cucComps>[\w\.]+)\.EnvelopesProvider");
56+
if (!globalVarsMatch.Success)
57+
throw new InvalidOperationException("Could not find global variables in main.js resource. The regex did not match: " + originalResource);
58+
var reactObj = globalVarsMatch.Groups["reactObj"].Value;
59+
var cucumberReactComponents = globalVarsMatch.Groups["cucComps"].Value;
60+
return
61+
"""
62+
function customRender0(reactObj, cucumberReactComponents, rootObj, renderArg) {
63+
rootObj.render(renderArg);
64+
}
65+
function customRender2(reactObj, cucumberReactComponents, rootObj, renderArg) {
66+
console.log(reactObj);
67+
console.log(cucumberReactComponents);
68+
console.log(rootObj);
69+
var customRenderArg =
70+
reactObj.createElement(cucumberReactComponents.CustomRendering, {
71+
overrides: {
72+
DocString: {
73+
docString: 'acme-docstring'
74+
}
75+
}
76+
},
77+
renderArg
78+
);
79+
rootObj.render(customRenderArg);
80+
}
81+
82+
function customRender(reactObj, cucumberReactComponents, rootObj, renderArg) {
83+
console.log(reactObj);
84+
console.log(cucumberReactComponents);
85+
console.log(rootObj);
86+
var customRenderArg =
87+
reactObj.createElement(cucumberReactComponents.CustomRendering, {
88+
overrides: {
89+
DocString: (props) => (
90+
reactObj.createElement(
91+
reactObj.Fragment,
92+
null,
93+
reactObj.createElement(
94+
"p",
95+
null,
96+
"I am going to render this doc string in a textarea:"
97+
),
98+
reactObj.createElement(
99+
"textarea",
100+
null,
101+
props.docString.content
102+
)
103+
)
104+
)
105+
}
106+
},
107+
renderArg
108+
);
109+
rootObj.render(customRenderArg);
110+
}
111+
112+
""" +
113+
Regex.Replace(originalResource, @"(?<rootObj>\(0,\w+\(\d+\).createRoot\)\(document.getElementById\(""content""\)\)).render\(", "customRender(" + reactObj + "," + cucumberReactComponents + ", ${rootObj},");
114+
}
115+
return originalResource;
116+
}
117+
}
118+
119+
protected override MessagesToHtmlWriter CreateMessagesToHtmlWriter(Stream stream, Func<StreamWriter, Envelope, Task> asyncStreamSerializer)
120+
=> new CustomizedMessagesToHtmlWriter(stream, asyncStreamSerializer);
121+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using Reqnroll.Formatters;
2+
using Reqnroll.Plugins;
3+
using Reqnroll.UnitTestProvider;
4+
using ReqnrollFormatters.CustomizedHtml;
5+
6+
[assembly: RuntimePlugin(typeof(CustomizedHtmlFormatterPlugin))]
7+
8+
namespace ReqnrollFormatters.CustomizedHtml;
9+
10+
public class CustomizedHtmlFormatterPlugin : IRuntimePlugin
11+
{
12+
public void Initialize(RuntimePluginEvents runtimePluginEvents, RuntimePluginParameters runtimePluginParameters, UnitTestProviderConfiguration unitTestProviderConfiguration)
13+
{
14+
runtimePluginEvents.RegisterGlobalDependencies += (_, args) =>
15+
{
16+
args.ObjectContainer.RegisterTypeAs<CustomizedHtmlFormatter, ICucumberMessageFormatter>("customizedHtml");
17+
};
18+
}
19+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@attachment
2+
Feature: Attachments
3+
4+
A short summary of the feature
5+
6+
Scenario: A scenario with an attachment
7+
When the step generates an attachment
8+
And the step generates a test output
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
Feature: Basics
2+
3+
This is the description of the **"basics" feature**.
4+
5+
* It includes scenarios to show *basic examples* for formatters.
6+
* This description is displayed as [Markdown](https://en.wikipedia.org/wiki/Markdown) in the formatter output.
7+
8+
@basic
9+
Scenario: Passing scenario
10+
Given the first parameter is "foo bar"
11+
And 42 is the second parameter
12+
When I do something
13+
Then the scenario passes
14+
15+
Scenario: Failing scenario
16+
When I do something
17+
Then the scenario fails
18+
And nothing else matters
19+
20+
Scenario Outline: Outline with multiple examples
21+
Given the first parameter is "<param>"
22+
And <other param> is the second parameter
23+
When I do something
24+
Then the scenario <result>
25+
Examples:
26+
| param | other param | result |
27+
| foo bar | 12 | passes |
28+
| baz | 23 | fails |
29+
30+
Scenario: Scenario with a doc-string
31+
When I do something
32+
Then the scenario passes with a doc-string
33+
"""
34+
This is a doc-string.
35+
It can contain multiple lines.
36+
"""
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
@dynamicignore
2+
Feature: DynamicIgnore
3+
4+
@ignore
5+
Scenario: Ignored scenario
6+
When I do something
7+
8+
Scenario Outline: Outline with ignored examples
9+
Given the first parameter is "<param>"
10+
And <other param> is the second parameter
11+
When I do something
12+
Then the scenario <result>
13+
Examples:
14+
| param | other param | result |
15+
| foo bar | 12 | passes |
16+
@ignore
17+
Examples: Ignored tests
18+
| param | other param | result |
19+
| hello | 12 | passes |
20+
21+
Scenario: Dynamically ignored
22+
Given the scenario is ignored
23+
When I do something
24+
25+
Scenario: Dynamically marked inconclusive
26+
Given the scenario is marked inconclusive
27+
When I do something
28+
29+
Scenario: Pending scenario
30+
Given the scenario is pending
31+
When I do something
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<TreatWarningsAsErrors>False</TreatWarningsAsErrors>
8+
<NoWarn>NU1605</NoWarn>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
13+
<PackageReference Include="Reqnroll.MsTest" Version="3.0.0-local" />
14+
<PackageReference Include="MSTest.TestAdapter" Version="3.9.3" />
15+
<PackageReference Include="MSTest.TestFramework" Version="3.9.3" />
16+
<PackageReference Include="Cucumber.HtmlFormatter" Version="21.13.0-local" />
17+
</ItemGroup>
18+
19+
<ItemGroup>
20+
<None Update="sample-image.png">
21+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
22+
</None>
23+
</ItemGroup>
24+
25+
</Project>
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
using Reqnroll;
2+
using Reqnroll.UnitTestProvider;
3+
4+
namespace ReqnrollFormatters.CustomizedHtml.StepDefinitions;
5+
6+
[Binding]
7+
public sealed class ProjectStepDefinitions(IReqnrollOutputHelper outputHelper, ScenarioContext scenarioContext)
8+
{
9+
[When("I do something")]
10+
public void WhenIDoSomething()
11+
{
12+
}
13+
14+
[Then("the scenario fails")]
15+
public void ThenTheScenarioFails()
16+
{
17+
throw new Exception("simulated error");
18+
}
19+
20+
[Then("nothing else matters")]
21+
public void ThenNothingElseMatters()
22+
{
23+
}
24+
25+
[Given("the first parameter is {string}")]
26+
public void GivenTheFirstParameterIs(string firstParam)
27+
{
28+
}
29+
30+
[Given("{int} is the second parameter")]
31+
public void WhenIsTheSecondParameter(int secondParam)
32+
{
33+
}
34+
35+
[Then("the scenario passes")]
36+
public void ThenTheScenarioPasses()
37+
{
38+
}
39+
40+
[Then("the scenario passes with a doc-string")]
41+
public void ThenTheScenarioPassesWithADoc_String(string multilineText)
42+
{
43+
}
44+
45+
[When("the step generates an attachment")]
46+
public void WhenTheStepGeneratesAnAttachment()
47+
{
48+
outputHelper.AddAttachment("sample-image.png");
49+
}
50+
51+
[When("the step generates a test output")]
52+
public void WhenTheStepGeneratesATestOutput()
53+
{
54+
outputHelper.WriteLine("this is a text" + Environment.NewLine + "this is a second line");
55+
}
56+
57+
[BeforeScenario("@attachment")]
58+
public void BeforeAttachmentScenario()
59+
{
60+
outputHelper.WriteLine("before attachment scenario hook executed");
61+
}
62+
63+
[AfterScenario("@attachment")]
64+
public void AfterAttachmentScenario()
65+
{
66+
outputHelper.WriteLine("after attachment scenario hook executed");
67+
}
68+
69+
[Given("the scenario is ignored")]
70+
public void GivenTheScenarioIsIgnored()
71+
{
72+
scenarioContext.ScenarioContainer.Resolve<IUnitTestRuntimeProvider>().TestIgnore("This is ignored!");
73+
}
74+
75+
[Given("the scenario is marked inconclusive")]
76+
public void GivenTheScenarioIsMarkedInconclusive()
77+
{
78+
scenarioContext.ScenarioContainer.Resolve<IUnitTestRuntimeProvider>().TestIgnore("This is inconclusive!");
79+
}
80+
81+
[Given("the scenario is pending")]
82+
public void GivenTheScenarioIsPending()
83+
{
84+
throw new PendingStepException();
85+
}
86+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"$schema": "https://schemas.reqnroll.net/reqnroll-config-latest.json",
3+
4+
"formatters": {
5+
"customizedHtml": {
6+
"outputFilePath": "custom_report.html"
7+
}
8+
}
9+
}
745 Bytes
Loading

ReqnrollFormatters/ReqnrollFormatters.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReqnrollFormatters.Standard
77
EndProject
88
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReqnrollFormatters.Custom", "ReqnrollFormatters.Custom\ReqnrollFormatters.Custom.csproj", "{FE6BE948-0314-11F3-BDA9-AE209CB7EC28}"
99
EndProject
10+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReqnrollFormatters.CustomizedHtml", "ReqnrollFormatters.CustomizedHtml\ReqnrollFormatters.CustomizedHtml.csproj", "{FA6A9313-663B-8AD5-B0C5-B6CD1BDBF8E4}"
11+
EndProject
1012
Global
1113
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1214
Debug|Any CPU = Debug|Any CPU
@@ -21,6 +23,10 @@ Global
2123
{FE6BE948-0314-11F3-BDA9-AE209CB7EC28}.Debug|Any CPU.Build.0 = Debug|Any CPU
2224
{FE6BE948-0314-11F3-BDA9-AE209CB7EC28}.Release|Any CPU.ActiveCfg = Release|Any CPU
2325
{FE6BE948-0314-11F3-BDA9-AE209CB7EC28}.Release|Any CPU.Build.0 = Release|Any CPU
26+
{FA6A9313-663B-8AD5-B0C5-B6CD1BDBF8E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27+
{FA6A9313-663B-8AD5-B0C5-B6CD1BDBF8E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
28+
{FA6A9313-663B-8AD5-B0C5-B6CD1BDBF8E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
29+
{FA6A9313-663B-8AD5-B0C5-B6CD1BDBF8E4}.Release|Any CPU.Build.0 = Release|Any CPU
2430
EndGlobalSection
2531
GlobalSection(SolutionProperties) = preSolution
2632
HideSolutionNode = FALSE

0 commit comments

Comments
 (0)