diff --git a/src/Pickles/Pickles.BaseDhtmlFiles/Index.html b/src/Pickles/Pickles.BaseDhtmlFiles/Index.html index 7b1c91122..49b86dae2 100644 --- a/src/Pickles/Pickles.BaseDhtmlFiles/Index.html +++ b/src/Pickles/Pickles.BaseDhtmlFiles/Index.html @@ -50,11 +50,27 @@ diff --git a/src/Pickles/Pickles.BaseDhtmlFiles/css/styles.css b/src/Pickles/Pickles.BaseDhtmlFiles/css/styles.css index 0f41b5d8c..1f5c182b9 100644 --- a/src/Pickles/Pickles.BaseDhtmlFiles/css/styles.css +++ b/src/Pickles/Pickles.BaseDhtmlFiles/css/styles.css @@ -72,6 +72,11 @@ li.step { padding: 0; } +.comment { + font-weight: bold; + color: #0088CC; +} + .keyword { font-weight: bold; color: #0000FF; diff --git a/src/Pickles/Pickles.BaseDhtmlFiles/js/featuresModel.js b/src/Pickles/Pickles.BaseDhtmlFiles/js/featuresModel.js index ccdd24895..4dee872e8 100644 --- a/src/Pickles/Pickles.BaseDhtmlFiles/js/featuresModel.js +++ b/src/Pickles/Pickles.BaseDhtmlFiles/js/featuresModel.js @@ -28,6 +28,12 @@ function Step(data) { this.NativeKeyword = data.NativeKeyword || ''; this.DocStringArgument = data.DocStringArgument || ''; this.TableArgument = data.TableArgument == null ? null : new TableArgument(data.TableArgument.HeaderRow, data.TableArgument.DataRows); + this.StepComments = data.StepComments == null ? null : $.map(data.StepComments, function (c) { return new Comment(c); }); + this.AfterLastStepComments = data.AfterLastStepComments == null ? null : $.map(data.AfterLastStepComments, function (c) { return new Comment(c); }); +} + +function Comment(data) { + this.Text = data.Text || ''; } function TableArgument(headerRow, dataRows) { diff --git a/src/Pickles/Pickles.ObjectModel/ObjectModel/Comment.cs b/src/Pickles/Pickles.ObjectModel/ObjectModel/Comment.cs new file mode 100644 index 000000000..d1a0afd88 --- /dev/null +++ b/src/Pickles/Pickles.ObjectModel/ObjectModel/Comment.cs @@ -0,0 +1,44 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright 2011 Jeffrey Cameron +// Copyright 2012-present PicklesDoc team and community contributors +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace PicklesDoc.Pickles.ObjectModel +{ + public enum CommentType + { + Normal, + StepComment, + AfterLastStepComment + } + + public class Comment + { + public Comment() + { + // Set default + this.Type = CommentType.Normal; + } + + public string Text { get; set; } + + public Location Location { get; set; } + + public CommentType Type { get; set; } + } +} diff --git a/src/Pickles/Pickles.ObjectModel/ObjectModel/Feature.cs b/src/Pickles/Pickles.ObjectModel/ObjectModel/Feature.cs index 7544d200e..1028133f7 100644 --- a/src/Pickles/Pickles.ObjectModel/ObjectModel/Feature.cs +++ b/src/Pickles/Pickles.ObjectModel/ObjectModel/Feature.cs @@ -27,6 +27,7 @@ public class Feature public Feature() { this.FeatureElements = new List(); + this.Comments = new List(); this.Tags = new List(); } @@ -36,6 +37,8 @@ public Feature() public List FeatureElements { get; } + public List Comments { get; } + public Scenario Background { get; private set; } public TestResult Result { get; set; } diff --git a/src/Pickles/Pickles.ObjectModel/ObjectModel/IFeatureElement.cs b/src/Pickles/Pickles.ObjectModel/ObjectModel/IFeatureElement.cs index 3242ce8c1..b510f8837 100644 --- a/src/Pickles/Pickles.ObjectModel/ObjectModel/IFeatureElement.cs +++ b/src/Pickles/Pickles.ObjectModel/ObjectModel/IFeatureElement.cs @@ -35,5 +35,7 @@ public interface IFeatureElement List Tags { get; set; } TestResult Result { get; set; } + + Location Location { get; set; } } } diff --git a/src/Pickles/Pickles.ObjectModel/ObjectModel/Location.cs b/src/Pickles/Pickles.ObjectModel/ObjectModel/Location.cs new file mode 100644 index 000000000..61c92f4e6 --- /dev/null +++ b/src/Pickles/Pickles.ObjectModel/ObjectModel/Location.cs @@ -0,0 +1,29 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright 2011 Jeffrey Cameron +// Copyright 2012-present PicklesDoc team and community contributors +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace PicklesDoc.Pickles.ObjectModel +{ + public class Location + { + public int Column { get; set; } + + public int Line { get; set; } + } +} diff --git a/src/Pickles/Pickles.ObjectModel/ObjectModel/Scenario.cs b/src/Pickles/Pickles.ObjectModel/ObjectModel/Scenario.cs index 47b3e699c..9584dc739 100644 --- a/src/Pickles/Pickles.ObjectModel/ObjectModel/Scenario.cs +++ b/src/Pickles/Pickles.ObjectModel/ObjectModel/Scenario.cs @@ -30,8 +30,6 @@ public Scenario() this.Tags = new List(); } - #region IFeatureElement Members - public string Name { get; set; } public string Description { get; set; } @@ -44,6 +42,6 @@ public Scenario() public Feature Feature { get; set; } - #endregion + public Location Location { get; set; } } } diff --git a/src/Pickles/Pickles.ObjectModel/ObjectModel/ScenarioOutline.cs b/src/Pickles/Pickles.ObjectModel/ObjectModel/ScenarioOutline.cs index 0ec3f70e7..470d12532 100644 --- a/src/Pickles/Pickles.ObjectModel/ObjectModel/ScenarioOutline.cs +++ b/src/Pickles/Pickles.ObjectModel/ObjectModel/ScenarioOutline.cs @@ -43,5 +43,7 @@ public ScenarioOutline() public TestResult Result { get; set; } public Feature Feature { get; set; } + + public Location Location { get; set; } } } diff --git a/src/Pickles/Pickles.ObjectModel/ObjectModel/Step.cs b/src/Pickles/Pickles.ObjectModel/ObjectModel/Step.cs index 37629f545..b4c09ff98 100644 --- a/src/Pickles/Pickles.ObjectModel/ObjectModel/Step.cs +++ b/src/Pickles/Pickles.ObjectModel/ObjectModel/Step.cs @@ -18,10 +18,17 @@ // // -------------------------------------------------------------------------------------------------------------------- +using System.Collections.Generic; + namespace PicklesDoc.Pickles.ObjectModel { public class Step { + public Step() + { + this.Comments = new List(); + } + public Keyword Keyword { get; set; } public string NativeKeyword { get; set; } @@ -31,5 +38,9 @@ public class Step public Table TableArgument { get; set; } public string DocStringArgument { get; set; } + + public Location Location { get; set; } + + public List Comments { get; set; } } } diff --git a/src/Pickles/Pickles.ObjectModel/Pickles.ObjectModel.csproj b/src/Pickles/Pickles.ObjectModel/Pickles.ObjectModel.csproj index 3c74cf1cf..4ad225c9b 100644 --- a/src/Pickles/Pickles.ObjectModel/Pickles.ObjectModel.csproj +++ b/src/Pickles/Pickles.ObjectModel/Pickles.ObjectModel.csproj @@ -46,6 +46,7 @@ + @@ -54,6 +55,7 @@ + diff --git a/src/Pickles/Pickles.Test/DocumentationBuilders/Excel/WhenAddingAStepToAWorksheet.cs b/src/Pickles/Pickles.Test/DocumentationBuilders/Excel/WhenAddingAStepToAWorksheet.cs index 0020fd958..9bbec6250 100644 --- a/src/Pickles/Pickles.Test/DocumentationBuilders/Excel/WhenAddingAStepToAWorksheet.cs +++ b/src/Pickles/Pickles.Test/DocumentationBuilders/Excel/WhenAddingAStepToAWorksheet.cs @@ -19,6 +19,8 @@ // -------------------------------------------------------------------------------------------------------------------- using System; +using System.Collections.Generic; +using System.Linq; using Autofac; using ClosedXML.Excel; using NFluent; @@ -47,5 +49,107 @@ public void ThenStepAddedSuccessfully() Check.That(worksheet.Cell("D5").Value).IsEqualTo(step.Name); } } + + [Test] + public void ThenStepCommentsAreAddedSuccessfully() + { + var excelStepFormatter = Container.Resolve(); + var step = new Step + { + NativeKeyword = "Given", + Name = "I have some precondition", + Comments = new List() + { + new Comment() + { + Text = "# A comment", + Type = CommentType.StepComment + } + } + }; + + using (var workbook = new XLWorkbook()) + { + IXLWorksheet worksheet = workbook.AddWorksheet("SHEET1"); + int row = 5; + excelStepFormatter.Format(worksheet, step, ref row); + + Check.That(worksheet.Cell("C5").Value).IsEqualTo(step.Comments.First().Text); + Check.That(worksheet.Cell("C6").Value).IsEqualTo(step.NativeKeyword); + Check.That(worksheet.Cell("D6").Value).IsEqualTo(step.Name); + } + } + + [Test] + public void ThenMultilineStepCommentsAreAddedSuccessfully() + { + var excelStepFormatter = Container.Resolve(); + var step = new Step + { + NativeKeyword = "Given", + Name = "I have some precondition", + Comments = new List() + { + new Comment() + { + Text = "# A comment - line 1", + Type = CommentType.StepComment + }, + new Comment() + { + Text = "# A comment - line 2", + Type = CommentType.StepComment + } + } + }; + + using (var workbook = new XLWorkbook()) + { + IXLWorksheet worksheet = workbook.AddWorksheet("SHEET1"); + int row = 5; + excelStepFormatter.Format(worksheet, step, ref row); + + Check.That(worksheet.Cell("C5").Value).IsEqualTo(step.Comments[0].Text); + Check.That(worksheet.Cell("C6").Value).IsEqualTo(step.Comments[1].Text); + Check.That(worksheet.Cell("C7").Value).IsEqualTo(step.NativeKeyword); + Check.That(worksheet.Cell("D7").Value).IsEqualTo(step.Name); + } + } + + [Test] + public void ThenCommentsAfterTheLastStepAreAddedSuccessfully() + { + var excelStepFormatter = Container.Resolve(); + var step = new Step + { + NativeKeyword = "Given", + Name = "I have some precondition", + Comments = new List() + { + new Comment() + { + Text = "# A comment", + Type = CommentType.StepComment + }, + new Comment() + { + Text = "# A comment the last step", + Type = CommentType.AfterLastStepComment + } + } + }; + + using (var workbook = new XLWorkbook()) + { + IXLWorksheet worksheet = workbook.AddWorksheet("SHEET1"); + int row = 5; + excelStepFormatter.Format(worksheet, step, ref row); + + Check.That(worksheet.Cell("C5").Value).IsEqualTo(step.Comments.First(o => o.Type == CommentType.StepComment).Text); + Check.That(worksheet.Cell("C6").Value).IsEqualTo(step.NativeKeyword); + Check.That(worksheet.Cell("D6").Value).IsEqualTo(step.Name); + Check.That(worksheet.Cell("C7").Value).IsEqualTo(step.Comments.First(o => o.Type == CommentType.AfterLastStepComment).Text); + } + } } } diff --git a/src/Pickles/Pickles.Test/DocumentationBuilders/HTML/WhenFormattingStep.cs b/src/Pickles/Pickles.Test/DocumentationBuilders/HTML/WhenFormattingStep.cs index 6f7aa0065..fa8e39609 100644 --- a/src/Pickles/Pickles.Test/DocumentationBuilders/HTML/WhenFormattingStep.cs +++ b/src/Pickles/Pickles.Test/DocumentationBuilders/HTML/WhenFormattingStep.cs @@ -33,6 +33,7 @@ namespace PicklesDoc.Pickles.Test.DocumentationBuilders.HTML public class WhenFormattingStep : BaseFixture { private const string ExpectedGivenHtml = "Given "; + private readonly XNamespace xmlns = XNamespace.Get("http://www.w3.org/1999/xhtml"); [Test] public void Multiline_strings_are_formatted_as_list_items_with_pre_elements_formatted_as_code_internal() @@ -49,7 +50,6 @@ public void Multiline_strings_are_formatted_as_list_items_with_pre_elements_form var formatter = Container.Resolve(); XElement actual = formatter.Format(step); - XNamespace xmlns = XNamespace.Get("http://www.w3.org/1999/xhtml"); var expected = new XElement( xmlns + "li", new XAttribute("class", "step"), @@ -84,7 +84,6 @@ public void Simple_steps_are_formatted_as_list_items() var formatter = Container.Resolve(); XElement actual = formatter.Format(step); - XNamespace xmlns = XNamespace.Get("http://www.w3.org/1999/xhtml"); var expected = new XElement( xmlns + "li", new XAttribute("class", "step"), @@ -112,7 +111,6 @@ public void Steps_get_selected_Language() var formatter = Container.Resolve(); XElement actual = formatter.Format(step); - XNamespace xmlns = XNamespace.Get("http://www.w3.org/1999/xhtml"); var expected = new XElement( xmlns + "li", new XAttribute("class", "step"), @@ -143,7 +141,6 @@ public void Tables_are_formatted_as_list_items_with_tables_internal() var formatter = Container.Resolve(); XElement actual = formatter.Format(step); - XNamespace xmlns = XNamespace.Get("http://www.w3.org/1999/xhtml"); var expected = new XElement( xmlns + "li", new XAttribute("class", "step"), @@ -181,5 +178,120 @@ public void Tables_are_formatted_as_list_items_with_tables_internal() Check.That(expected).IsDeeplyEqualTo(actual); } + + [Test] + public void Comments_are_displayed_above_their_related_step() + { + var step = new Step + { + Keyword = Keyword.Given, + NativeKeyword = "Given ", + Name = "a simple step", + TableArgument = null, + DocStringArgument = null, + Comments = new List + { + new Comment + { + Text = " # A simple comment", + Type = CommentType.StepComment + } + } + }; + + var formatter = Container.Resolve(); + XElement actual = formatter.Format(step); + + var expected = new XElement( + xmlns + "li", + new XAttribute("class", "step"), + new XElement(xmlns + "span", new XAttribute("class", "comment"), "# A simple comment"), + new XElement(xmlns + "span", new XAttribute("class", "keyword"), ExpectedGivenHtml), + "a simple step"); + + Check.That(expected).IsDeeplyEqualTo(actual); + } + + [Test] + public void Comments_after_the_last_step_are_displayed() + { + var step = new Step + { + Keyword = Keyword.Given, + NativeKeyword = "Given ", + Name = "a simple step", + TableArgument = null, + DocStringArgument = null, + Comments = new List + { + new Comment + { + Text = " # A simple comment", + Type = CommentType.StepComment + }, + new Comment + { + Text = " # A comment after the last step", + Type = CommentType.AfterLastStepComment + } + } + }; + + var formatter = Container.Resolve(); + XElement actual = formatter.Format(step); + + var expected = new XElement( + xmlns + "li", + new XAttribute("class", "step"), + new XElement(xmlns + "span", new XAttribute("class", "comment"), "# A simple comment"), + new XElement(xmlns + "span", new XAttribute("class", "keyword"), ExpectedGivenHtml), + "a simple step", + new XElement(xmlns + "span", new XAttribute("class", "comment"), "# A comment after the last step")); + + Check.That(expected).IsDeeplyEqualTo(actual); + } + + [Test] + public void Multiline_comments_are_displayed_in_the_same_element() + { + var step = new Step + { + Keyword = Keyword.Given, + NativeKeyword = "Given ", + Name = "a simple step", + TableArgument = null, + DocStringArgument = null, + Comments = new List + { + new Comment + { + Text = " # A first line", + Type = CommentType.StepComment + }, + new Comment + { + Text = " # A second line", + Type = CommentType.StepComment + } + } + }; + + var formatter = Container.Resolve(); + XElement actual = formatter.Format(step); + + var expected = new XElement( + xmlns + "li", + new XAttribute("class", "step"), + new XElement(xmlns + "span", new XAttribute("class", "comment"), + "# A first line", + new XElement(xmlns + "br"), + "# A second line" + ), + new XElement(xmlns + "span", new XAttribute("class", "keyword"), ExpectedGivenHtml), + "a simple step"); + + Check.That(expected).IsDeeplyEqualTo(actual); + } } } + diff --git a/src/Pickles/Pickles.Test/DocumentationBuilders/JSON/FormattingAFeature.feature b/src/Pickles/Pickles.Test/DocumentationBuilders/JSON/FormattingAFeature.feature index 6762a572d..5ff313a4b 100644 --- a/src/Pickles/Pickles.Test/DocumentationBuilders/JSON/FormattingAFeature.feature +++ b/src/Pickles/Pickles.Test/DocumentationBuilders/JSON/FormattingAFeature.feature @@ -35,22 +35,30 @@ Scenario: A simple feature { "Keyword": "Given", "NativeKeyword": "Given ", - "Name": "I have entered 50 into the calculator" + "Name": "I have entered 50 into the calculator", + "StepComments": [], + "AfterLastStepComments": [] }, { "Keyword": "And", "NativeKeyword": "And ", - "Name": "I have entered 70 into the calculator" + "Name": "I have entered 70 into the calculator", + "StepComments": [], + "AfterLastStepComments": [] }, { "Keyword": "When", "NativeKeyword": "When ", - "Name": "I press C" + "Name": "I press C", + "StepComments": [], + "AfterLastStepComments": [] }, { "Keyword": "Then", "NativeKeyword": "Then ", - "Name": "the screen should be empty" + "Name": "the screen should be empty", + "StepComments": [], + "AfterLastStepComments": [] } ], "Tags": [ @@ -230,17 +238,23 @@ Scenario: A feature with a table "105" ] ] - } + }, + "StepComments": [], + "AfterLastStepComments": [] }, { "Keyword": "When", "NativeKeyword": "When ", - "Name": "I click on the table heading" + "Name": "I click on the table heading", + "StepComments": [], + "AfterLastStepComments": [] }, { "Keyword": "Then", "NativeKeyword": "Then ", - "Name": "the table body should collapse" + "Name": "the table body should collapse", + "StepComments": [], + "AfterLastStepComments": [] } ], "Tags": [], diff --git a/src/Pickles/Pickles.Test/DocumentationBuilders/JSON/FormattingAFeature.feature.cs b/src/Pickles/Pickles.Test/DocumentationBuilders/JSON/FormattingAFeature.feature.cs index 76c58c303..1d07e5ee2 100644 --- a/src/Pickles/Pickles.Test/DocumentationBuilders/JSON/FormattingAFeature.feature.cs +++ b/src/Pickles/Pickles.Test/DocumentationBuilders/JSON/FormattingAFeature.feature.cs @@ -96,20 +96,24 @@ When I press C "r the screen\",\r\n \"FeatureElements\": [\r\n {\r\n \"Name\": \"" + "Clear the screen\",\r\n \"Description\": \"\",\r\n \"Steps\": [\r\n " + " {\r\n \"Keyword\": \"Given\",\r\n \"NativeKeyword" + - "\": \"Given \",\r\n \"Name\": \"I have entered 50 into the calculator\"\r\n " + - " },\r\n {\r\n \"Keyword\": \"And\",\r\n " + - " \"NativeKeyword\": \"And \",\r\n \"Name\": \"I have entered 70 into t" + - "he calculator\"\r\n },\r\n {\r\n \"Keyword\": \"W" + - "hen\",\r\n \"NativeKeyword\": \"When \",\r\n \"Name\": \"I pre" + - "ss C\"\r\n },\r\n {\r\n \"Keyword\": \"Then\",\r\n " + - " \"NativeKeyword\": \"Then \",\r\n \"Name\": \"the screen sho" + - "uld be empty\"\r\n }\r\n ],\r\n \"Tags\": [\r\n " + - " \"@workflow\",\r\n \"@slow\"\r\n ],\r\n \"Result\": " + - "{\r\n \"WasExecuted\": false,\r\n \"WasSuccessful\": false\r\n " + - " }\r\n }\r\n ],\r\n \"Result\": {\r\n \"WasExecute" + - "d\": false,\r\n \"WasSuccessful\": false\r\n },\r\n \"Tags\": []\r\n " + - " },\r\n \"Result\": {\r\n \"WasExecuted\": false,\r\n \"WasSuccessful" + - "\": false\r\n }\r\n }\r\n ],", ((TechTalk.SpecFlow.Table)(null)), "Then "); + "\": \"Given \",\r\n \"Name\": \"I have entered 50 into the calculator\",\r\n" + + " \"StepComments\": [],\r\n \"AfterLastStepComments\": []" + + "\r\n },\r\n {\r\n \"Keyword\": \"And\",\r\n " + + " \"NativeKeyword\": \"And \",\r\n \"Name\": \"I have entered 70 int" + + "o the calculator\",\r\n \"StepComments\": [],\r\n \"AfterL" + + "astStepComments\": []\r\n },\r\n {\r\n \"Keywor" + + "d\": \"When\",\r\n \"NativeKeyword\": \"When \",\r\n \"Name\": " + + "\"I press C\",\r\n \"StepComments\": [],\r\n \"AfterLastSte" + + "pComments\": []\r\n },\r\n {\r\n \"Keyword\": \"T" + + "hen\",\r\n \"NativeKeyword\": \"Then \",\r\n \"Name\": \"the s" + + "creen should be empty\",\r\n \"StepComments\": [],\r\n \"A" + + "fterLastStepComments\": []\r\n }\r\n ],\r\n \"Tags\": " + + "[\r\n \"@workflow\",\r\n \"@slow\"\r\n ],\r\n " + + " \"Result\": {\r\n \"WasExecuted\": false,\r\n \"WasSuccessful" + + "\": false\r\n }\r\n }\r\n ],\r\n \"Result\": {\r\n " + + " \"WasExecuted\": false,\r\n \"WasSuccessful\": false\r\n },\r\n \"T" + + "ags\": []\r\n },\r\n \"Result\": {\r\n \"WasExecuted\": false,\r\n \"W" + + "asSuccessful\": false\r\n }\r\n }\r\n ],", ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } @@ -121,10 +125,10 @@ public virtual void AFeatureWithATable() { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("A feature with a table", new string[] { "json"}); -#line 81 +#line 89 this.ScenarioSetup(scenarioInfo); #line hidden -#line 83 +#line 91 testRunner.Given("I have this feature description", @"Feature: Interactive DHTML View In order to increase stakeholder engagement with pickled specs As a SpecFlow evangelist @@ -158,10 +162,10 @@ I want to adjust the level of detail in the DHTML view to suit my audience | Chapter 22 | 105 | When I click on the table heading Then the table body should collapse", ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 119 +#line 127 testRunner.When("I generate the documentation", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line hidden -#line 120 +#line 128 testRunner.Then("the JSON file should contain", "{\r\n \"Features\": [\r\n {\r\n \"RelativeFolder\": \"\",\r\n \"Feature\": {\r\n " + " \"Name\": \"Interactive DHTML View\",\r\n \"Description\": \" In order to in" + "crease stakeholder engagement with pickled specs\\r\\n As a SpecFlow evangelist" + @@ -204,16 +208,19 @@ When I click on the table heading "[\r\n \"Chapter 21\",\r\n \"100\"\r\n " + " ],\r\n [\r\n \"Chapter 22\",\r\n " + " \"105\"\r\n ]\r\n ]\r\n " + - " }\r\n },\r\n {\r\n \"Keyword\": \"When\",\r\n " + - " \"NativeKeyword\": \"When \",\r\n \"Name\": \"I click on the t" + - "able heading\"\r\n },\r\n {\r\n \"Keyword\": \"Th" + - "en\",\r\n \"NativeKeyword\": \"Then \",\r\n \"Name\": \"the ta" + - "ble body should collapse\"\r\n }\r\n ],\r\n \"Tags\": " + - "[],\r\n \"Result\": {\r\n \"WasExecuted\": false,\r\n " + - " \"WasSuccessful\": false\r\n }\r\n }\r\n ],\r\n \"Result" + - "\": {\r\n \"WasExecuted\": false,\r\n \"WasSuccessful\": false\r\n " + - " },\r\n \"Tags\": []\r\n },\r\n \"Result\": {\r\n \"WasExecuted\": fal" + - "se,\r\n \"WasSuccessful\": false\r\n }\r\n }\r\n ],", ((TechTalk.SpecFlow.Table)(null)), "Then "); + " },\r\n \"StepComments\": [],\r\n \"AfterLastStepComment" + + "s\": []\r\n },\r\n {\r\n \"Keyword\": \"When\",\r\n " + + " \"NativeKeyword\": \"When \",\r\n \"Name\": \"I click on th" + + "e table heading\",\r\n \"StepComments\": [],\r\n \"AfterLa" + + "stStepComments\": []\r\n },\r\n {\r\n \"Keyword" + + "\": \"Then\",\r\n \"NativeKeyword\": \"Then \",\r\n \"Name\": \"" + + "the table body should collapse\",\r\n \"StepComments\": [],\r\n " + + " \"AfterLastStepComments\": []\r\n }\r\n ],\r\n " + + " \"Tags\": [],\r\n \"Result\": {\r\n \"WasExecuted\": false,\r\n " + + " \"WasSuccessful\": false\r\n }\r\n }\r\n ],\r\n " + + " \"Result\": {\r\n \"WasExecuted\": false,\r\n \"WasSuccessful\": false" + + "\r\n },\r\n \"Tags\": []\r\n },\r\n \"Result\": {\r\n \"WasExecu" + + "ted\": false,\r\n \"WasSuccessful\": false\r\n }\r\n }\r\n ],", ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } diff --git a/src/Pickles/Pickles.Test/HtmlFormatterTestFiles/Comment.feature b/src/Pickles/Pickles.Test/HtmlFormatterTestFiles/Comment.feature new file mode 100644 index 000000000..88236250e --- /dev/null +++ b/src/Pickles/Pickles.Test/HtmlFormatterTestFiles/Comment.feature @@ -0,0 +1,25 @@ +Feature: Test + In order to do something + As a user + I want to run this scenario + + Scenario: A scenario + # A single line comment + Given some feature + # A multiline comment - first line + # second line + And another feature + # + # Multiline with empty first line + # But the last lines are not empty + And another feature + # Multiline with empty last line + # But the first lines are not empty + # + When it runs + # Multiline with first and last last lines not empty + # + # But the middle line is empty + Then I should see that this thing happens + And there is no comment here + # A comment after the last step \ No newline at end of file diff --git a/src/Pickles/Pickles.Test/HtmlFormatterTestFiles/Comment.html b/src/Pickles/Pickles.Test/HtmlFormatterTestFiles/Comment.html new file mode 100644 index 000000000..0dd51bb87 --- /dev/null +++ b/src/Pickles/Pickles.Test/HtmlFormatterTestFiles/Comment.html @@ -0,0 +1,51 @@ + + + + + Feature: Test + + + + Feature: Test + In order to do something + As a user + I want to run this scenario + + + + + Scenario: A scenario + + + + + + # A single line comment + Given some feature + + + # A multiline comment - first line# second line + And another feature + + + # Multiline with empty first line# But the last lines are not empty + And another feature + + + # Multiline with empty last line# But the first lines are not empty + When it runs + + + # Multiline with first and last last lines not empty# But the middle line is empty + Then I should see that this thing happens + + + And there is no comment here + # A comment after the last step + + + + + + + \ No newline at end of file diff --git a/src/Pickles/Pickles.Test/ObjectModel/Factory.cs b/src/Pickles/Pickles.Test/ObjectModel/Factory.cs index 04926abaa..3757058b1 100644 --- a/src/Pickles/Pickles.Test/ObjectModel/Factory.cs +++ b/src/Pickles/Pickles.Test/ObjectModel/Factory.cs @@ -66,6 +66,12 @@ internal G.Step CreateStep(string keyword, string text) return new G.Step(AnyLocation, keyword, text, null); } + internal G.Step CreateStep(string keyword, string text, int locationLine, int locationColumn) + { + var step = new G.Step(this.CreateLocation(locationLine, locationColumn), keyword, text, null); + return step; + } + internal G.Step CreateStep(string keyword, string text, string docString) { return new G.Step(AnyLocation, keyword, text, this.CreateDocString(docString)); @@ -81,11 +87,21 @@ internal G.Tag CreateTag(string tag) return new G.Tag(AnyLocation, tag); } - internal G.Scenario CreateScenario(string[] tags, string name, string description, G.Step[] steps) + internal G.Location CreateLocation(int line, int column) + { + return new G.Location(line, column); + } + + internal G.Comment CreateComment(string comment, int locationLine, int locationColumn) + { + return new G.Comment(this.CreateLocation(locationLine, locationColumn), comment); + } + + internal G.Scenario CreateScenario(string[] tags, string name, string description, G.Step[] steps, G.Location location = null) { G.Scenario scenario = new G.Scenario( tags.Select(this.CreateTag).ToArray(), - AnyLocation, + location ?? AnyLocation, "Scenario", name, description, @@ -131,9 +147,9 @@ internal G.Background CreateBackground(string name, string description, G.Step[] return background; } - internal G.Feature CreateFeature(string name, string description, string[] tags = null, G.Background background = null, G.ScenarioDefinition[] scenarioDefinitions = null) + internal G.Feature CreateFeature(string name, string description, string[] tags = null, G.Background background = null, G.ScenarioDefinition[] scenarioDefinitions = null, G.Comment[] comments = null, G.Location location = null) { - return new G.Feature((tags ?? new string[0]).Select(this.CreateTag).ToArray(), null, null, "Feature", name, description, background, scenarioDefinitions, null); + return new G.Feature((tags ?? new string[0]).Select(this.CreateTag).ToArray(), location, null, "Feature", name, description, background, scenarioDefinitions, comments); } } } diff --git a/src/Pickles/Pickles.Test/ObjectModel/MapperTestsForComment.cs b/src/Pickles/Pickles.Test/ObjectModel/MapperTestsForComment.cs new file mode 100644 index 000000000..97156349f --- /dev/null +++ b/src/Pickles/Pickles.Test/ObjectModel/MapperTestsForComment.cs @@ -0,0 +1,35 @@ +using System; +using NFluent; +using NUnit.Framework; +using PicklesDoc.Pickles.ObjectModel; +using G = Gherkin.Ast; + +namespace PicklesDoc.Pickles.Test.ObjectModel +{ + [TestFixture] + public class MapperTestsForComment + { + private readonly Factory factory = new Factory(); + + [Test] + public void MapToComment_NullLocation_ReturnsNull() + { + var mapper = this.factory.CreateMapper(); + Comment result = mapper.MapToComment(null); + Check.That(result).IsNull(); + } + + [Test] + public void MapToComment_RegularComment_ReturnsComment() + { + var mapper = this.factory.CreateMapper(); + G.Comment comment = this.factory.CreateComment("# A comment", 1, 2); + Comment result = mapper.MapToComment(comment); + Check.That(result).IsNotNull(); + Check.That(result.Text).IsEqualTo("# A comment"); + Check.That(result.Location.Line).IsEqualTo(1); + Check.That(result.Location.Column).IsEqualTo(2); + Check.That(result.Type).IsEqualTo(CommentType.Normal); + } + } +} diff --git a/src/Pickles/Pickles.Test/ObjectModel/MapperTestsForFeature.cs b/src/Pickles/Pickles.Test/ObjectModel/MapperTestsForFeature.cs index a225ad17e..8a8f767f1 100644 --- a/src/Pickles/Pickles.Test/ObjectModel/MapperTestsForFeature.cs +++ b/src/Pickles/Pickles.Test/ObjectModel/MapperTestsForFeature.cs @@ -19,6 +19,7 @@ // -------------------------------------------------------------------------------------------------------------------- using System; +using System.Linq; using NFluent; using NUnit.Framework; using PicklesDoc.Pickles.ObjectModel; @@ -114,5 +115,62 @@ public void MapToFeature_FeatureWithNullDescription_ReturnsFeatureWithEmptyDescr Check.That(result.Description).Equals(string.Empty); } + + [Test] + public void MapToFeature_FeatureWithComments_ReturnsFeatureWithComments() + { + var feature = this.factory.CreateFeature( + "My Feature", + string.Empty, + location: new G.Location(2, 0), + comments: new G.Comment[] + { + this.factory.CreateComment("# single line comment before the given step", 4, 4), + this.factory.CreateComment("# multiline comment before the then step - line 1", 6, 4), + this.factory.CreateComment("# multiline comment before the then step - line 2", 7, 4), + this.factory.CreateComment("# line comment before the last step", 10, 4), + this.factory.CreateComment("# line comment after the last step", 12, 4), + }, + scenarioDefinitions: new G.ScenarioDefinition[] + { + this.factory.CreateScenario( + new string[0], "My scenario", string.Empty, + new G.Step[] + { + this.factory.CreateStep("Given", "I am on the first step", 5, 4), + this.factory.CreateStep("When", "I am on the second step", 8, 4), + this.factory.CreateStep("When", "there is a third step without comment", 9, 4), + this.factory.CreateStep("Then", "I am on the last step", 11, 4) + }, + location: new G.Location(3, 0) + ) + }); + + var mapper = this.factory.CreateMapper(); + + var result = mapper.MapToFeature(feature); + var scenario = result.FeatureElements[0]; + + Check.That(scenario.Steps[0].Comments.Count).IsEqualTo(1); + Check.That(scenario.Steps[0].Comments.Count(o => o.Type == CommentType.StepComment)).IsEqualTo(1); + Check.That(scenario.Steps[0].Comments.Count(o => o.Type == CommentType.AfterLastStepComment)).IsEqualTo(0); + Check.That(scenario.Steps[0].Comments[0].Text).IsEqualTo("# single line comment before the given step"); + + Check.That(scenario.Steps[1].Comments.Count).IsEqualTo(2); + Check.That(scenario.Steps[1].Comments.Count(o => o.Type == CommentType.StepComment)).IsEqualTo(2); + Check.That(scenario.Steps[1].Comments.Count(o => o.Type == CommentType.AfterLastStepComment)).IsEqualTo(0); + Check.That(scenario.Steps[1].Comments[0].Text).IsEqualTo("# multiline comment before the then step - line 1"); + Check.That(scenario.Steps[1].Comments[1].Text).IsEqualTo("# multiline comment before the then step - line 2"); + + Check.That(scenario.Steps[2].Comments.Count).IsEqualTo(0); + Check.That(scenario.Steps[2].Comments.Count(o => o.Type == CommentType.StepComment)).IsEqualTo(0); + Check.That(scenario.Steps[2].Comments.Count(o => o.Type == CommentType.AfterLastStepComment)).IsEqualTo(0); + + Check.That(scenario.Steps[3].Comments.Count).IsEqualTo(2); + Check.That(scenario.Steps[3].Comments.Count(o => o.Type == CommentType.StepComment)).IsEqualTo(1); + Check.That(scenario.Steps[3].Comments.Count(o => o.Type == CommentType.AfterLastStepComment)).IsEqualTo(1); + Check.That(scenario.Steps[3].Comments[0].Text).IsEqualTo("# line comment before the last step"); + Check.That(scenario.Steps[3].Comments[1].Text).IsEqualTo("# line comment after the last step"); + } } -} +} \ No newline at end of file diff --git a/src/Pickles/Pickles.Test/ObjectModel/MapperTestsForLocation.cs b/src/Pickles/Pickles.Test/ObjectModel/MapperTestsForLocation.cs new file mode 100644 index 000000000..235d5b97f --- /dev/null +++ b/src/Pickles/Pickles.Test/ObjectModel/MapperTestsForLocation.cs @@ -0,0 +1,33 @@ +using System; +using NFluent; +using NUnit.Framework; +using PicklesDoc.Pickles.ObjectModel; +using G = Gherkin.Ast; + +namespace PicklesDoc.Pickles.Test.ObjectModel +{ + [TestFixture] + public class MapperTestsForLocation + { + private readonly Factory factory = new Factory(); + + [Test] + public void MapToLocation_NullLocation_ReturnsNull() + { + var mapper = this.factory.CreateMapper(); + Location result = mapper.MapToLocation(null); + Check.That(result).IsNull(); + } + + [Test] + public void MapToLocation_RegularLocation_ReturnsLocation() + { + var mapper = this.factory.CreateMapper(); + G.Location location = this.factory.CreateLocation(1, 2); + Location result = mapper.MapToLocation(location); + Check.That(result).IsNotNull(); + Check.That(result.Line).IsEqualTo(1); + Check.That(result.Column).IsEqualTo(2); + } + } +} diff --git a/src/Pickles/Pickles.Test/ObjectModel/MapperTestsForStep.cs b/src/Pickles/Pickles.Test/ObjectModel/MapperTestsForStep.cs index d41579509..2ee25a1a1 100644 --- a/src/Pickles/Pickles.Test/ObjectModel/MapperTestsForStep.cs +++ b/src/Pickles/Pickles.Test/ObjectModel/MapperTestsForStep.cs @@ -55,6 +55,8 @@ public void MapToStep_StepWithoutArgument_ReturnStep() Check.That(result.Name).IsEqualTo("I enter '50' in the calculator"); Check.That(result.DocStringArgument).IsNull(); Check.That(result.TableArgument).IsNull(); + Check.That(result.Location).IsNull(); + Check.That(result.Comments).IsEmpty(); } [Test] @@ -71,6 +73,8 @@ public void MapToStep_StepWithWhen_ReturnStep() Check.That(result.Name).IsEqualTo("I press 'enter'"); Check.That(result.DocStringArgument).IsNull(); Check.That(result.TableArgument).IsNull(); + Check.That(result.Location).IsNull(); + Check.That(result.Comments).IsEmpty(); } [Test] @@ -107,6 +111,8 @@ public void MapToStep_StepWithDocStringArgument_ReturnStepWithDocString() Check.That(result.Name).IsEqualTo("I see this value on the screen"); Check.That(result.DocStringArgument).IsEqualTo("120"); Check.That(result.TableArgument).IsNull(); + Check.That(result.Location).IsNull(); + Check.That(result.Comments).IsEmpty(); } [Test] @@ -132,6 +138,30 @@ public void MapToStep_StepWithTableArgument_ReturnStepWithTable() Check.That(result.TableArgument.DataRows).HasSize(1); Check.That(result.TableArgument.DataRows[0].Cells).ContainsExactly("Value 1", "Value 2"); Check.That(result.DocStringArgument).IsNull(); + Check.That(result.Location).IsNull(); + Check.That(result.Comments).IsEmpty(); + } + + [Test] + public void MapToStep_StepWithLocation_ReturnStepWithLocation() + { + var mapper = this.factory.CreateMapper(); + + G.Step step = this.factory.CreateStep( + "Given", "I am on a step", 3, 4 + ); + + var result = mapper.MapToStep(step); + + Check.That(result.Keyword).IsEqualTo(Keyword.Given); + Check.That(result.NativeKeyword).IsEqualTo("Given"); + Check.That(result.Name).IsEqualTo("I am on a step"); + Check.That(result.DocStringArgument).IsNull(); + Check.That(result.TableArgument).IsNull(); + Check.That(result.Location).IsNotNull(); + Check.That(result.Location.Line).IsEqualTo(3); + Check.That(result.Location.Column).IsEqualTo(4); + Check.That(result.Comments).IsEmpty(); } } } diff --git a/src/Pickles/Pickles.Test/Pickles.Test.csproj b/src/Pickles/Pickles.Test/Pickles.Test.csproj index 3e4854a19..ff69a9a11 100644 --- a/src/Pickles/Pickles.Test/Pickles.Test.csproj +++ b/src/Pickles/Pickles.Test/Pickles.Test.csproj @@ -115,11 +115,13 @@ + + @@ -217,6 +219,7 @@ SpecFlowSingleFileGenerator FormattingAFeature.feature.cs + ResXFileCodeGenerator @@ -226,6 +229,7 @@ SimplestFile.feature.cs + SimplestFile.feature.cs diff --git a/src/Pickles/Pickles.Test/WhenParsingFeatureFiles.cs b/src/Pickles/Pickles.Test/WhenParsingFeatureFiles.cs index 79bfd49f7..c5f62dbd6 100644 --- a/src/Pickles/Pickles.Test/WhenParsingFeatureFiles.cs +++ b/src/Pickles/Pickles.Test/WhenParsingFeatureFiles.cs @@ -353,5 +353,47 @@ When it runs Check.That(feature.FeatureElements[0].Tags[0]).IsEqualTo("@scenario-tag-1"); Check.That(feature.FeatureElements[0].Tags[1]).IsEqualTo("@scenario-tag-2"); } + + [Test] + public void Then_can_parse_scenario_with_comments_successfully() + { + string featureText = + @"# ignore this comment +Feature: Test + In order to do something + As a user + I want to run this scenario + + Scenario: A scenario + # A single line comment + Given some feature + # A multiline comment - first line + # Second line + When it runs + Then I should see that this thing happens + # A last comment after the scenario"; + + var parser = Container.Resolve(); + Feature feature = parser.Parse(new StringReader(featureText)); + + IFeatureElement scenario = feature.FeatureElements.First(); + + Step stepGiven = scenario.Steps[0]; + Check.That(stepGiven.Comments.Count).IsEqualTo(1); + Check.That(stepGiven.Comments[0].Text).IsEqualTo("# A single line comment"); + + Step stepWhen = scenario.Steps[1]; + Check.That(stepWhen.Comments.Count).IsEqualTo(2); + Check.That(stepWhen.Comments[0].Text).IsEqualTo("# A multiline comment - first line"); + Check.That(stepWhen.Comments[1].Text).IsEqualTo("# Second line"); + + Step stepThen = scenario.Steps[2]; + Check.That(stepThen.Comments.Count).IsEqualTo(1); + Check.That(stepThen.Comments.Count(o => o.Type == CommentType.StepComment)).IsEqualTo(0); + Check.That(stepThen.Comments.Count(o => o.Type == CommentType.AfterLastStepComment)).IsEqualTo(1); + Check.That(stepThen.Comments[0].Text = "# A last comment after the scenario"); + } + + } } diff --git a/src/Pickles/Pickles/DocumentationBuilders/Excel/ExcelStepFormatter.cs b/src/Pickles/Pickles/DocumentationBuilders/Excel/ExcelStepFormatter.cs index 7306cf7c6..63aaf51cc 100644 --- a/src/Pickles/Pickles/DocumentationBuilders/Excel/ExcelStepFormatter.cs +++ b/src/Pickles/Pickles/DocumentationBuilders/Excel/ExcelStepFormatter.cs @@ -19,6 +19,7 @@ // -------------------------------------------------------------------------------------------------------------------- using System; +using System.Linq; using ClosedXML.Excel; using PicklesDoc.Pickles.ObjectModel; @@ -40,11 +41,35 @@ public ExcelStepFormatter( public void Format(IXLWorksheet worksheet, Step step, ref int row) { + // Add comments + if (step.Comments.Any(o => o.Type == CommentType.StepComment)) + { + foreach (var comment in step.Comments.Where(o => o.Type == CommentType.StepComment)) + { + worksheet.Cell(row, "C").Style.Font.SetItalic(); + worksheet.Cell(row, "C").Style.Alignment.SetHorizontal(XLAlignmentHorizontalValues.Left); + worksheet.Cell(row, "C").Value = comment.Text; + row++; + } + } + worksheet.Cell(row, "C").Style.Font.SetBold(); worksheet.Cell(row, "C").Style.Alignment.SetHorizontal(XLAlignmentHorizontalValues.Right); worksheet.Cell(row, "C").Value = step.NativeKeyword; worksheet.Cell(row++, "D").Value = step.Name; + if (step.Comments.Any(o => o.Type == CommentType.AfterLastStepComment)) + { + foreach (var comment in step.Comments.Where(o => o.Type == CommentType.AfterLastStepComment)) + { + worksheet.Cell(row, "C").Style.Font.SetItalic(); + worksheet.Cell(row, "C").Style.Alignment.SetHorizontal(XLAlignmentHorizontalValues.Left); + worksheet.Cell(row, "C").Value = comment.Text; + row++; + } + } + + if (step.TableArgument != null) { this.excelTableFormatter.Format(worksheet, step.TableArgument, ref row); diff --git a/src/Pickles/Pickles/DocumentationBuilders/HTML/HtmlStepFormatter.cs b/src/Pickles/Pickles/DocumentationBuilders/HTML/HtmlStepFormatter.cs index 2a4d9883d..0024f4c62 100644 --- a/src/Pickles/Pickles/DocumentationBuilders/HTML/HtmlStepFormatter.cs +++ b/src/Pickles/Pickles/DocumentationBuilders/HTML/HtmlStepFormatter.cs @@ -19,8 +19,8 @@ // -------------------------------------------------------------------------------------------------------------------- using System; +using System.Linq; using System.Xml.Linq; - using PicklesDoc.Pickles.ObjectModel; namespace PicklesDoc.Pickles.DocumentationBuilders.HTML @@ -45,13 +45,42 @@ public HtmlStepFormatter( this.xmlns = HtmlNamespace.Xhtml; } + protected XElement FormatComments(Step step, CommentType type) + { + XElement comment = new XElement(this.xmlns + "span", new XAttribute("class", "comment")); + + foreach (var stepComment in step.Comments.Where(o => o.Type == type)) + { + comment.Add(stepComment.Text.Trim()); + comment.Add(new XElement(this.xmlns + "br")); + } + comment.LastNode.Remove(); + + return comment; + } + public XElement Format(Step step) { - var li = new XElement( - this.xmlns + "li", - new XAttribute("class", "step"), - new XElement(this.xmlns + "span", new XAttribute("class", "keyword"), step.NativeKeyword), - step.Name); + XElement li; + + XElement beforeStepComments = null; + XElement afterStepComments = null; + if (step.Comments.Any(o => o.Type == CommentType.StepComment)) + { + beforeStepComments = this.FormatComments(step, CommentType.StepComment); + } + if (step.Comments.Any(o => o.Type == CommentType.AfterLastStepComment)) + { + afterStepComments = this.FormatComments(step, CommentType.AfterLastStepComment); + } + + li = new XElement( + this.xmlns + "li", + new XAttribute("class", "step"), + beforeStepComments, + new XElement(this.xmlns + "span", new XAttribute("class", "keyword"), step.NativeKeyword), + step.Name, + afterStepComments); if (step.TableArgument != null) { diff --git a/src/Pickles/Pickles/DocumentationBuilders/JSON/JsonComment.cs b/src/Pickles/Pickles/DocumentationBuilders/JSON/JsonComment.cs new file mode 100644 index 000000000..2d6efde3f --- /dev/null +++ b/src/Pickles/Pickles/DocumentationBuilders/JSON/JsonComment.cs @@ -0,0 +1,31 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright 2011 Jeffrey Cameron +// Copyright 2012-present PicklesDoc team and community contributors +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// -------------------------------------------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using PicklesDoc.Pickles.ObjectModel; + +namespace PicklesDoc.Pickles.DocumentationBuilders.JSON +{ + public class JsonComment + { + public string Text { get; set; } + } +} diff --git a/src/Pickles/Pickles/DocumentationBuilders/JSON/JsonMapper.cs b/src/Pickles/Pickles/DocumentationBuilders/JSON/JsonMapper.cs index 1010a036b..48751a5dc 100644 --- a/src/Pickles/Pickles/DocumentationBuilders/JSON/JsonMapper.cs +++ b/src/Pickles/Pickles/DocumentationBuilders/JSON/JsonMapper.cs @@ -19,6 +19,8 @@ // -------------------------------------------------------------------------------------------------------------------- using System; +using System.Collections.Generic; +using System.Linq; using AutoMapper; using AutoMapper.Mappers; using PicklesDoc.Pickles.ObjectModel; @@ -52,7 +54,17 @@ public JsonMapper() .ForMember(t => t.Feature, opt => opt.Ignore()); configurationStore.CreateMap() .ForMember(t => t.Feature, opt => opt.Ignore()); - configurationStore.CreateMap(); + configurationStore.CreateMap(); + configurationStore.CreateMap() + .ForMember(t => t.StepComments, opt => opt.UseValue(new List())) + .ForMember(t => t.AfterLastStepComments, opt => opt.UseValue(new List())) + .AfterMap( + (sourceStep, targetStep) => + { + this.mapper.Map(sourceStep.Comments.Where(o => o.Type == CommentType.StepComment), targetStep.StepComments); + this.mapper.Map(sourceStep.Comments.Where(o => o.Type == CommentType.AfterLastStepComment), targetStep.AfterLastStepComments); + } + ); configurationStore.CreateMap(); configurationStore.CreateMap().ConstructUsing(ToJsonTestResult); diff --git a/src/Pickles/Pickles/DocumentationBuilders/JSON/JsonStep.cs b/src/Pickles/Pickles/DocumentationBuilders/JSON/JsonStep.cs index cbe1d3854..142880616 100644 --- a/src/Pickles/Pickles/DocumentationBuilders/JSON/JsonStep.cs +++ b/src/Pickles/Pickles/DocumentationBuilders/JSON/JsonStep.cs @@ -19,6 +19,8 @@ // -------------------------------------------------------------------------------------------------------------------- using System; +using System.Collections.Generic; +using PicklesDoc.Pickles.ObjectModel; namespace PicklesDoc.Pickles.DocumentationBuilders.JSON { @@ -33,5 +35,9 @@ public class JsonStep public JsonTable TableArgument { get; set; } public string DocStringArgument { get; set; } + + public List StepComments { get; set; } + + public List AfterLastStepComments { get; set; } } } diff --git a/src/Pickles/Pickles/DocumentationBuilders/Word/WordStepFormatter.cs b/src/Pickles/Pickles/DocumentationBuilders/Word/WordStepFormatter.cs index 05300dcef..13ac60dae 100644 --- a/src/Pickles/Pickles/DocumentationBuilders/Word/WordStepFormatter.cs +++ b/src/Pickles/Pickles/DocumentationBuilders/Word/WordStepFormatter.cs @@ -19,6 +19,7 @@ // -------------------------------------------------------------------------------------------------------------------- using System; +using System.Linq; using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.Wordprocessing; using PicklesDoc.Pickles.Extensions; @@ -61,10 +62,35 @@ public static Paragraph GenerateStepParagraph(Step step) { // HACK - We need to generate a custom paragraph here because 2 Run objects are needed to allow for the bolded keyword var paragraph = new Paragraph(new ParagraphProperties(new ParagraphStyleId { Val = "Normal" })); + + // Add comments before step + if (step.Comments.Any(o => o.Type == CommentType.StepComment)) + { + foreach (var comment in step.Comments.Where(o => o.Type == CommentType.StepComment)) + { + paragraph.Append(new Run(new RunProperties(new Italic()), new Text(comment.Text))); + paragraph.Append(new Break()); + } + } + + // Add step paragraph.Append(new Run(new RunProperties(new Bold()), new Text(step.NativeKeyword))); var nameText = new Text { Space = SpaceProcessingModeValues.Preserve }; nameText.Text = " " + step.Name; paragraph.Append(new Run(nameText)); + + // Add comments after step + if (step.Comments.Any(o => o.Type == CommentType.AfterLastStepComment)) + { + paragraph.Append(new Break()); + + foreach (var comment in step.Comments.Where(o => o.Type == CommentType.AfterLastStepComment)) + { + paragraph.Append(new Run(new RunProperties(new Italic()), new Text(comment.Text))); + paragraph.Append(new Break()); + } + } + return paragraph; } } diff --git a/src/Pickles/Pickles/ObjectModel/Mapper.cs b/src/Pickles/Pickles/ObjectModel/Mapper.cs index 7b234bca4..97813b99b 100644 --- a/src/Pickles/Pickles/ObjectModel/Mapper.cs +++ b/src/Pickles/Pickles/ObjectModel/Mapper.cs @@ -51,9 +51,24 @@ public Mapper(string featureLanguage = LanguageServices.DefaultLanguage) configurationStore.CreateMap().ConstructUsing(docString => docString.Content); + configurationStore.CreateMap() + .ForMember(t => t.Column, opt => opt.MapFrom(s => s.Column)) + .ForMember(t => t.Line, opt => opt.MapFrom(s => s.Line)); + + configurationStore.CreateMap() + .ForMember(t => t.Text, opt => opt.MapFrom(s => s.Text)) + .ForMember(t => t.Location, opt => opt.MapFrom(s => s.Location)) + .AfterMap( + (sourceComment, targetComment) => + { + targetComment.Text = targetComment.Text.Trim(); + } + ); + configurationStore.CreateMap() .ForMember(t => t.NativeKeyword, opt => opt.MapFrom(s => s.Keyword)) .ForMember(t => t.Name, opt => opt.MapFrom(s => s.Text)) + .ForMember(t => t.Location, opt => opt.MapFrom(s => s.Location)) .ForMember(t => t.DocStringArgument, opt => opt.MapFrom(s => s.Argument is G.DocString ? s.Argument : null)) .ForMember(t => t.TableArgument, opt => opt.MapFrom(s => s.Argument is G.DataTable ? s.Argument : null)); @@ -61,7 +76,8 @@ public Mapper(string featureLanguage = LanguageServices.DefaultLanguage) .ConstructUsing(tag => tag.Name); configurationStore.CreateMap() - .ForMember(t => t.Description, opt => opt.NullSubstitute(string.Empty)); + .ForMember(t => t.Description, opt => opt.NullSubstitute(string.Empty)) + .ForMember(t => t.Location, opt => opt.MapFrom(s => s.Location)); configurationStore.CreateMap, Table>() .ForMember(t => t.HeaderRow, opt => opt.MapFrom(s => s.Take(1).Single())) @@ -71,7 +87,8 @@ public Mapper(string featureLanguage = LanguageServices.DefaultLanguage) .ForMember(t => t.TableArgument, opt => opt.MapFrom(s => ((G.IHasRows)s).Rows)); configurationStore.CreateMap() - .ForMember(t => t.Description, opt => opt.NullSubstitute(string.Empty)); + .ForMember(t => t.Description, opt => opt.NullSubstitute(string.Empty)) + .ForMember(t => t.Location, opt => opt.MapFrom(s => s.Location)); configurationStore.CreateMap() .ForMember(t => t.Description, opt => opt.NullSubstitute(string.Empty)); @@ -97,9 +114,38 @@ public Mapper(string featureLanguage = LanguageServices.DefaultLanguage) configurationStore.CreateMap() .ForMember(t => t.FeatureElements, opt => opt.ResolveUsing(s => s.ScenarioDefinitions)) .ForMember(t => t.Description, opt => opt.NullSubstitute(string.Empty)) + .ForMember(t => t.Comments, opt => opt.ResolveUsing(s => s.Comments)) .AfterMap( (sourceFeature, targetFeature) => { + foreach (var comment in targetFeature.Comments.ToArray()) + { + // Find the related feature + var relatedFeatureElement = targetFeature.FeatureElements.LastOrDefault(x => x.Location.Line < comment.Location.Line); + // Find the step to which the comment is related to + if (relatedFeatureElement != null) + { + var stepAfterComment = relatedFeatureElement.Steps.FirstOrDefault(x => x.Location.Line > comment.Location.Line); + if (stepAfterComment != null) + { + // Comment is before a step + comment.Type = CommentType.StepComment; + stepAfterComment.Comments.Add(comment); + } + else + { + // Comment is located after the last step + var stepBeforeComment = relatedFeatureElement.Steps.LastOrDefault(x => x.Location.Line < comment.Location.Line); + if (stepBeforeComment != null && stepBeforeComment == relatedFeatureElement.Steps.Last()) + { + + comment.Type = CommentType.AfterLastStepComment; + stepBeforeComment.Comments.Add(comment); + } + } + } + } + foreach (var featureElement in targetFeature.FeatureElements.ToArray()) { featureElement.Feature = targetFeature; @@ -147,6 +193,16 @@ public string MapToString(G.Tag tag) return this.mapper.Map(tag); } + public Comment MapToComment(G.Comment comment) + { + return this.mapper.Map(comment); + } + + public Location MapToLocation(G.Location location) + { + return this.mapper.Map(location); + } + public Scenario MapToScenario(G.Scenario scenario) { return this.mapper.Map(scenario); diff --git a/src/Pickles/Pickles/Pickles.csproj b/src/Pickles/Pickles/Pickles.csproj index b92a5ec1a..0b24b586c 100644 --- a/src/Pickles/Pickles/Pickles.csproj +++ b/src/Pickles/Pickles/Pickles.csproj @@ -152,6 +152,7 @@ + diff --git a/src/Pickles/Pickles/Resources/Html/css/structure.css b/src/Pickles/Pickles/Resources/Html/css/structure.css index a63b0b0cf..c217734cb 100644 --- a/src/Pickles/Pickles/Resources/Html/css/structure.css +++ b/src/Pickles/Pickles/Resources/Html/css/structure.css @@ -232,6 +232,12 @@ li.scenario, div.scenario { font-weight: bold; } +.comment { + display: block; + font-weight: normal; + color: gray; +} + /************************************************************************* * Footer *************************************************************************/
In order to do something
As a user
I want to run this scenario