diff --git a/CHANGELOG.md b/CHANGELOG.md index a453ff5562..007f523b4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - [JUnit Platform Engine] Use JUnit Platform 1.11.3 (JUnit Jupiter 5.11.3) +### Added +- [Core] Pretty-Print DocStringArgument Step Arguments([#2953](https://github.com/cucumber/cucumber-jvm/pull/2953) Daniel Miladinov) ## [7.20.1] - 2024-10-09 ### Fixed diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/PrettyFormatter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/PrettyFormatter.java index a37c03513f..a0bb1b74c9 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/PrettyFormatter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/PrettyFormatter.java @@ -2,8 +2,11 @@ import io.cucumber.core.exception.CucumberException; import io.cucumber.core.gherkin.DataTableArgument; +import io.cucumber.core.gherkin.DocStringArgument; import io.cucumber.datatable.DataTable; import io.cucumber.datatable.DataTableFormatter; +import io.cucumber.docstring.DocString; +import io.cucumber.docstring.DocStringFormatter; import io.cucumber.plugin.ColorAware; import io.cucumber.plugin.ConcurrentEventListener; import io.cucumber.plugin.event.Argument; @@ -141,15 +144,29 @@ private void printStep(TestStepFinished event) { String locationComment = formatLocationComment(event, testStep, keyword, stepText); out.println(STEP_INDENT + formattedStepText + locationComment); StepArgument stepArgument = testStep.getStep().getArgument(); - if (DataTableArgument.class.isInstance(stepArgument)) { + if (stepArgument instanceof DataTableArgument) { DataTableFormatter tableFormatter = DataTableFormatter .builder() .prefixRow(STEP_SCENARIO_INDENT) .escapeDelimiters(false) .build(); DataTableArgument dataTableArgument = (DataTableArgument) stepArgument; + DataTable table = DataTable.create(dataTableArgument.cells()); try { - tableFormatter.formatTo(DataTable.create(dataTableArgument.cells()), out); + tableFormatter.formatTo(table, out); + } catch (IOException e) { + throw new CucumberException(e); + } + } else if (stepArgument instanceof DocStringArgument) { + DocStringFormatter docStringFormatter = DocStringFormatter + .builder() + .indentation(STEP_SCENARIO_INDENT) + .build(); + DocStringArgument docStringArgument = (DocStringArgument) stepArgument; + DocString docString = DocString.create(docStringArgument.getContent(), + docStringArgument.getContentType()); + try { + docStringFormatter.formatTo(docString, out); } catch (IOException e) { throw new CucumberException(e); } diff --git a/cucumber-core/src/test/java/io/cucumber/core/plugin/PrettyFormatterTest.java b/cucumber-core/src/test/java/io/cucumber/core/plugin/PrettyFormatterTest.java index f274c2dc70..4a27d0cfe3 100755 --- a/cucumber-core/src/test/java/io/cucumber/core/plugin/PrettyFormatterTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/plugin/PrettyFormatterTest.java @@ -16,6 +16,7 @@ import io.cucumber.core.stepexpression.StepExpressionFactory; import io.cucumber.core.stepexpression.StepTypeRegistry; import io.cucumber.datatable.DataTable; +import io.cucumber.docstring.DocString; import org.junit.jupiter.api.Test; import java.io.ByteArrayOutputStream; @@ -611,4 +612,36 @@ void should_print_system_failure_for_failed_hooks() { " " + AnsiEscapes.RED + "the stack trace" + AnsiEscapes.RESET))); } + @Test + void should_print_docstring_including_content_type() { + Feature feature = TestFeatureParser.parse("path/test.feature", "" + + "Feature: Test feature\n" + + " Scenario: Test Scenario\n" + + " Given first step\n" + + " \"\"\"json\n" + + " {\"key1\": \"value1\",\n" + + " \"key2\": \"value2\",\n" + + " \"another1\": \"another2\"}\n" + + " \"\"\"\n"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Runtime.builder() + .withFeatureSupplier(new StubFeatureSupplier(feature)) + .withAdditionalPlugins(new PrettyFormatter(out)) + .withRuntimeOptions(new RuntimeOptionsBuilder().setMonochrome().build()) + .withBackendSupplier(new StubBackendSupplier( + new StubStepDefinition("first step", "path/step_definitions.java:7", DocString.class))) + .build() + .run(); + + assertThat(out, bytes(equalToCompressingWhiteSpace("" + + "\n" + + "Scenario: Test Scenario # path/test.feature:2\n" + + " Given first step # path/step_definitions.java:7\n" + + " \"\"\"json\n" + + " {\"key1\": \"value1\",\n" + + " \"key2\": \"value2\",\n" + + " \"another1\": \"another2\"}\n" + + " \"\"\"\n"))); + } } diff --git a/docstring/src/main/java/io/cucumber/docstring/DocString.java b/docstring/src/main/java/io/cucumber/docstring/DocString.java index 852062db40..992661667a 100644 --- a/docstring/src/main/java/io/cucumber/docstring/DocString.java +++ b/docstring/src/main/java/io/cucumber/docstring/DocString.java @@ -5,9 +5,7 @@ import java.lang.reflect.Type; import java.util.Objects; -import static java.util.Arrays.stream; import static java.util.Objects.requireNonNull; -import static java.util.stream.Collectors.joining; /** * A doc string. For example: @@ -81,11 +79,9 @@ public boolean equals(Object o) { @Override public String toString() { - return stream(content.split("\n")) - .collect(joining( - "\n ", - " \"\"\"" + contentType + "\n ", - "\n \"\"\"")); + return DocStringFormatter.builder() + .build() + .format(this); } public interface DocStringConverter { diff --git a/docstring/src/main/java/io/cucumber/docstring/DocStringFormatter.java b/docstring/src/main/java/io/cucumber/docstring/DocStringFormatter.java new file mode 100644 index 0000000000..0909f14d4f --- /dev/null +++ b/docstring/src/main/java/io/cucumber/docstring/DocStringFormatter.java @@ -0,0 +1,65 @@ +package io.cucumber.docstring; + +import org.apiguardian.api.API; + +import java.io.IOException; + +import static java.util.Objects.requireNonNull; + +@API(status = API.Status.EXPERIMENTAL) +public final class DocStringFormatter { + + private final String indentation; + + private DocStringFormatter(String indentation) { + this.indentation = indentation; + } + + public static DocStringFormatter.Builder builder() { + return new Builder(); + } + + public String format(DocString docString) { + StringBuilder result = new StringBuilder(); + formatTo(docString, result); + return result.toString(); + } + + public void formatTo(DocString docString, StringBuilder appendable) { + requireNonNull(docString, "docString may not be null"); + requireNonNull(appendable, "appendable may not be null"); + try { + formatTo(docString, (Appendable) appendable); + } catch (IOException e) { + throw new CucumberDocStringException(e.getMessage(), e); + } + } + + public void formatTo(DocString docString, Appendable out) throws IOException { + String printableContentType = docString.getContentType() == null ? "" : docString.getContentType(); + out.append(indentation).append("\"\"\"").append(printableContentType).append("\n"); + for (String l : docString.getContent().split("\\n")) { + out.append(indentation).append(l).append("\n"); + } + out.append(indentation).append("\"\"\"").append("\n"); + } + + public static final class Builder { + + private String indentation = ""; + + private Builder() { + + } + + public Builder indentation(String indentation) { + requireNonNull(indentation, "indentation may not be null"); + this.indentation = indentation; + return this; + } + + public DocStringFormatter build() { + return new DocStringFormatter(indentation); + } + } +} diff --git a/docstring/src/test/java/io/cucumber/docstring/DocStringFormatterTest.java b/docstring/src/test/java/io/cucumber/docstring/DocStringFormatterTest.java new file mode 100644 index 0000000000..ce9841a8fa --- /dev/null +++ b/docstring/src/test/java/io/cucumber/docstring/DocStringFormatterTest.java @@ -0,0 +1,64 @@ +package io.cucumber.docstring; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; + +class DocStringFormatterTest { + + @Test + void should_print_docstring_with_content_type() { + DocString docString = DocString.create("{\n" + + " \"key1\": \"value1\",\n" + + " \"key2\": \"value2\",\n" + + " \"another1\": \"another2\"\n" + + "}\n", + "application/json"); + + DocStringFormatter formatter = DocStringFormatter.builder().build(); + String format = formatter.format(docString); + assertThat(format, equalTo( + "\"\"\"application/json\n" + + "{\n" + + " \"key1\": \"value1\",\n" + + " \"key2\": \"value2\",\n" + + " \"another1\": \"another2\"\n" + + "}\n" + + "\"\"\"\n")); + } + + @Test + void should_print_docstring_without_content_type() { + DocString docString = DocString.create("{\n" + + " \"key1\": \"value1\",\n" + + " \"key2\": \"value2\",\n" + + " \"another1\": \"another2\"\n" + + "}\n"); + + DocStringFormatter formatter = DocStringFormatter.builder().build(); + String format = formatter.format(docString); + assertThat(format, equalTo( + "\"\"\"\n" + + "{\n" + + " \"key1\": \"value1\",\n" + + " \"key2\": \"value2\",\n" + + " \"another1\": \"another2\"\n" + + "}\n" + + "\"\"\"\n")); + } + + @Test + void should_print_docstring_with_indentation() { + DocString docString = DocString.create("Hello", + "text/plain"); + + DocStringFormatter formatter = DocStringFormatter.builder().indentation(" ").build(); + String format = formatter.format(docString); + assertThat(format, equalTo( + " \"\"\"text/plain\n" + + " Hello\n" + + " \"\"\"\n")); + } + +} diff --git a/docstring/src/test/java/io/cucumber/docstring/DocStringTest.java b/docstring/src/test/java/io/cucumber/docstring/DocStringTest.java index dcf8454d0d..68fd10fba8 100644 --- a/docstring/src/test/java/io/cucumber/docstring/DocStringTest.java +++ b/docstring/src/test/java/io/cucumber/docstring/DocStringTest.java @@ -28,11 +28,11 @@ void pretty_prints_doc_string_objects() { "application/json"); assertThat(docString.toString(), is("" + - " \"\"\"application/json\n" + - " {\n" + - " \"hello\":\"world\"\n" + - " }\n" + - " \"\"\"")); + "\"\"\"application/json\n" + + "{\n" + + " \"hello\":\"world\"\n" + + "}\n" + + "\"\"\"\n")); } @Test @@ -60,9 +60,9 @@ void pretty_prints_empty_doc_string_objects() { "application/json"); assertThat(docString.toString(), is("" + - " \"\"\"application/json\n" + - " \n" + - " \"\"\"")); + "\"\"\"application/json\n" + + "\n" + + "\"\"\"\n")); } } diff --git a/docstring/src/test/java/io/cucumber/docstring/DocStringTypeRegistryDocStringConverterTest.java b/docstring/src/test/java/io/cucumber/docstring/DocStringTypeRegistryDocStringConverterTest.java index 0df1b77bd7..50b02c7fbd 100644 --- a/docstring/src/test/java/io/cucumber/docstring/DocStringTypeRegistryDocStringConverterTest.java +++ b/docstring/src/test/java/io/cucumber/docstring/DocStringTypeRegistryDocStringConverterTest.java @@ -151,9 +151,9 @@ void throws_when_conversion_fails() { () -> converter.convert(docString, JsonNode.class)); assertThat(exception.getMessage(), is(equalToCompressingWhiteSpace("" + "'json' could not transform\n" + - " \"\"\"json\n" + - " {\"hello\":\"world\"}\n" + - " \"\"\""))); + " \"\"\"json\n" + + " {\"hello\":\"world\"}\n" + + " \"\"\""))); } @Test