diff --git a/java/.mvn/jvm.config b/java/.mvn/jvm.config
new file mode 100644
index 00000000..32599cef
--- /dev/null
+++ b/java/.mvn/jvm.config
@@ -0,0 +1,10 @@
+--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
+--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
+--add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
diff --git a/java/pom.xml b/java/pom.xml
index e407e686..1e5e1d3c 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -5,11 +5,11 @@
io.cucumber
cucumber-parent
- 4.5.0
+ 5.0.0-SNAPSHOT
html-formatter
- 22.0.1-SNAPSHOT
+ 23.0.0-SNAPSHOT
jar
Cucumber HTML Formatter
Renders Cucumber Messages as HTML
@@ -44,6 +44,14 @@
pom
import
+
+
+ org.assertj
+ assertj-bom
+ 3.27.6
+ pom
+ import
+
@@ -51,7 +59,7 @@
io.cucumber
messages
- [18.0.0,31.0.0)
+ [32.0.0-SNAPSHOT,33.0.0)
@@ -73,9 +81,8 @@
- org.hamcrest
- hamcrest
- 3.0
+ org.assertj
+ assertj-core
test
@@ -85,4 +92,22 @@
test
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+
+
+ validate
+ validate
+
+ check
+
+
+
+
+
+
diff --git a/java/src/main/java/io/cucumber/htmlformatter/JsonInHtmlWriter.java b/java/src/main/java/io/cucumber/htmlformatter/JsonInHtmlWriter.java
index 5ff47cef..e771b2bd 100644
--- a/java/src/main/java/io/cucumber/htmlformatter/JsonInHtmlWriter.java
+++ b/java/src/main/java/io/cucumber/htmlformatter/JsonInHtmlWriter.java
@@ -1,7 +1,10 @@
package io.cucumber.htmlformatter;
+import org.jspecify.annotations.Nullable;
+
import java.io.IOException;
import java.io.Writer;
+import java.util.Objects;
/**
* Writes json with the forward slash ({@code /}) escaped. Assumes
@@ -10,10 +13,10 @@
class JsonInHtmlWriter extends Writer {
private static final int BUFFER_SIZE = 1024;
private final Writer delegate;
- private char[] escapeBuffer;
+ private char @Nullable[] escapeBuffer;
JsonInHtmlWriter(Writer delegate) {
- this.delegate = delegate;
+ this.delegate = Objects.requireNonNull(delegate);
}
@Override
diff --git a/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java b/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java
index e46bf3f2..08108c5b 100644
--- a/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java
+++ b/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java
@@ -1,6 +1,7 @@
package io.cucumber.htmlformatter;
import io.cucumber.messages.types.Envelope;
+import org.jspecify.annotations.Nullable;
import java.io.BufferedReader;
import java.io.BufferedWriter;
@@ -39,20 +40,6 @@ public final class MessagesToHtmlWriter implements AutoCloseable {
private boolean firstMessageWritten = false;
private boolean streamClosed = false;
- @Deprecated
- public MessagesToHtmlWriter(OutputStream outputStream, Serializer serializer) throws IOException {
- this(
- createWriter(outputStream),
- requireNonNull(serializer),
- () -> createInputStream("Cucumber"),
- () -> getResource("icon.url"),
- () -> getResource("main.css"),
- MessagesToHtmlWriter::createEmptyInputStream,
- () -> getResource("main.js"),
- MessagesToHtmlWriter::createEmptyInputStream
- );
- }
-
private MessagesToHtmlWriter(
OutputStreamWriter writer,
Serializer serializer,
@@ -88,7 +75,7 @@ private static String readTemplate() {
InputStream resource = getResource("index.mustache.html");
writeResource(writer, resource);
}
- return new String(baos.toByteArray(), UTF_8);
+ return baos.toString(UTF_8);
} catch (IOException e) {
throw new RuntimeException("Could not read resource index.mustache.html", e);
}
@@ -108,24 +95,14 @@ private static InputStream getResource(String name) {
return resource;
}
- private void writePreMessage() throws IOException {
- writeTemplateBetween(writer, template, null, "{{title}}");
- writeResource(writer, title);
- writeTemplateBetween(writer, template, "{{title}}", "{{icon}}");
- writeResource(writer, icon);
- writeTemplateBetween(writer, template, "{{icon}}", "{{css}}");
- writeResource(writer, css);
- writeTemplateBetween(writer, template, "{{css}}", "{{custom_css}}");
- writeResource(writer, customCss);
- writeTemplateBetween(writer, template, "{{custom_css}}", "{{messages}}");
- }
-
- private void writePostMessage() throws IOException {
- writeTemplateBetween(writer, template, "{{messages}}", "{{script}}");
- writeResource(writer, script);
- writeTemplateBetween(writer, template, "{{script}}", "{{custom_script}}");
- writeResource(writer, customScript);
- writeTemplateBetween(writer, template, "{{custom_script}}", null);
+ /**
+ * Creates a builder to construct this writer.
+ *
+ * @param serializer used to convert messages into json.
+ * @return a new builder
+ */
+ public static Builder builder(Serializer serializer) {
+ return new Builder(serializer);
}
/**
@@ -153,6 +130,18 @@ public void write(Envelope envelope) throws IOException {
serializer.writeValue(jsonInHtmlWriter, envelope);
}
+ private void writePreMessage() throws IOException {
+ writeTemplateBetween(writer, template, null, "{{title}}");
+ writeResource(writer, title);
+ writeTemplateBetween(writer, template, "{{title}}", "{{icon}}");
+ writeResource(writer, icon);
+ writeTemplateBetween(writer, template, "{{icon}}", "{{css}}");
+ writeResource(writer, css);
+ writeTemplateBetween(writer, template, "{{css}}", "{{custom_css}}");
+ writeResource(writer, customCss);
+ writeTemplateBetween(writer, template, "{{custom_css}}", "{{messages}}");
+ }
+
/**
* Closes the stream, flushing it first. Once closed further write()
* invocations will cause an IOException to be thrown. Closing a closed
@@ -183,7 +172,15 @@ public void close() throws IOException {
}
}
- private static void writeTemplateBetween(Writer writer, String template, String begin, String end)
+ private void writePostMessage() throws IOException {
+ writeTemplateBetween(writer, template, "{{messages}}", "{{script}}");
+ writeResource(writer, script);
+ writeTemplateBetween(writer, template, "{{script}}", "{{custom_script}}");
+ writeResource(writer, customScript);
+ writeTemplateBetween(writer, template, "{{custom_script}}", null);
+ }
+
+ private static void writeTemplateBetween(Writer writer, String template, @Nullable String begin, @Nullable String end)
throws IOException {
int beginIndex = begin == null ? 0 : template.indexOf(begin) + begin.length();
int endIndex = end == null ? template.length() : template.indexOf(end);
@@ -229,16 +226,6 @@ public interface Serializer {
}
- /**
- * Creates a builder to construct this writer.
- *
- * @param serializer used to convert messages into json.
- * @return a new builder
- */
- public static Builder builder(Serializer serializer) {
- return new Builder(serializer);
- }
-
public static final class Builder {
private final Serializer serializer;
private Supplier title = () -> createInputStream("Cucumber");
diff --git a/java/src/main/java/io/cucumber/htmlformatter/package-info.java b/java/src/main/java/io/cucumber/htmlformatter/package-info.java
new file mode 100644
index 00000000..b0acaa72
--- /dev/null
+++ b/java/src/main/java/io/cucumber/htmlformatter/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package io.cucumber.htmlformatter;
+
+import org.jspecify.annotations.NullMarked;
\ No newline at end of file
diff --git a/java/src/test/java/io/cucumber/htmlformatter/Jackson.java b/java/src/test/java/io/cucumber/htmlformatter/Jackson.java
index 60407c65..313a9f06 100644
--- a/java/src/test/java/io/cucumber/htmlformatter/Jackson.java
+++ b/java/src/test/java/io/cucumber/htmlformatter/Jackson.java
@@ -1,7 +1,6 @@
package io.cucumber.htmlformatter;
import com.fasterxml.jackson.annotation.JsonCreator.Mode;
-import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -11,11 +10,14 @@
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
+import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_ABSENT;
+import static com.fasterxml.jackson.annotation.JsonInclude.Value.construct;
+
final class Jackson {
public static final ObjectMapper OBJECT_MAPPER = JsonMapper.builder()
.addModule(new Jdk8Module())
.addModule(new ParameterNamesModule(Mode.PROPERTIES))
- .serializationInclusion(Include.NON_ABSENT)
+ .defaultPropertyInclusion(construct(NON_ABSENT, NON_ABSENT))
.constructorDetector(ConstructorDetector.USE_PROPERTIES_BASED)
.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING)
.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING)
diff --git a/java/src/test/java/io/cucumber/htmlformatter/JsonInHtmlWriterTest.java b/java/src/test/java/io/cucumber/htmlformatter/JsonInHtmlWriterTest.java
index 6d540205..a33fdf26 100644
--- a/java/src/test/java/io/cucumber/htmlformatter/JsonInHtmlWriterTest.java
+++ b/java/src/test/java/io/cucumber/htmlformatter/JsonInHtmlWriterTest.java
@@ -59,12 +59,8 @@ void large_writes_with_odd_boundaries() throws IOException {
Arrays.fill(buffer, 1, buffer.length, '<');
writer.write(buffer);
- StringBuilder expected = new StringBuilder();
- expected.append("a");
- for (int i = 1; i < buffer.length; i++) {
- expected.append("\\x3C");
- }
- assertEquals(expected.toString(), output());
+ String expected = "a" + "\\x3C".repeat(buffer.length - 1);
+ assertEquals(expected, output());
}
@@ -74,11 +70,7 @@ void really_large_writes() throws IOException {
Arrays.fill(buffer, '<');
writer.write(buffer);
- StringBuilder expected = new StringBuilder();
- for (int i = 0; i < buffer.length; i++) {
- expected.append("\\x3C");
- }
- assertEquals(expected.toString(), output());
+ assertEquals("\\x3C".repeat(buffer.length), output());
}
@Test
@@ -90,6 +82,6 @@ void empty_write() throws IOException {
private String output() throws IOException {
writer.flush();
- return new String(out.toByteArray(), UTF_8);
+ return out.toString(UTF_8);
}
}
diff --git a/java/src/test/java/io/cucumber/htmlformatter/Main.java b/java/src/test/java/io/cucumber/htmlformatter/Main.java
index 14b53eec..89545612 100644
--- a/java/src/test/java/io/cucumber/htmlformatter/Main.java
+++ b/java/src/test/java/io/cucumber/htmlformatter/Main.java
@@ -4,25 +4,34 @@
import io.cucumber.messages.NdjsonToMessageIterable;
import io.cucumber.messages.NdjsonToMessageIterable.Deserializer;
import io.cucumber.messages.types.Envelope;
+import org.jspecify.annotations.NullMarked;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import static io.cucumber.htmlformatter.Jackson.OBJECT_MAPPER;
public final class Main {
- private static final Deserializer deserializer = (json) -> OBJECT_MAPPER.readValue(json, Envelope.class);
+ private static final Deserializer deserializer = json -> OBJECT_MAPPER.readValue(json, Envelope.class);
private static final Serializer serializer = OBJECT_MAPPER::writeValue;
+ private Main() {
+ // main class
+ }
+
public static void main(String[] args) throws IOException {
- InputStream in = System.in;
- if (args.length == 1) {
+ InputStream in;
+ if (args.length != 1) {
+ in = new NonClosableInputStream(System.in);
+ } else {
in = new FileInputStream(args[0]);
}
try (NdjsonToMessageIterable envelopes = new NdjsonToMessageIterable(in, deserializer)) {
MessagesToHtmlWriter.Builder builder = MessagesToHtmlWriter.builder(serializer);
- try (MessagesToHtmlWriter htmlWriter = builder.build(System.out)) {
+ OutputStream out = new NonClosableOutputStream(System.out);
+ try (MessagesToHtmlWriter htmlWriter = builder.build(out)) {
for (Envelope envelope : envelopes) {
htmlWriter.write(envelope);
}
@@ -33,4 +42,44 @@ public static void main(String[] args) throws IOException {
System.exit(1);
}
}
+
+ @NullMarked
+ private static class NonClosableInputStream extends InputStream {
+
+ private final InputStream delegate;
+
+ NonClosableInputStream(InputStream delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public int read() throws IOException {
+ return delegate.read();
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ return delegate.read(b, off, len);
+ }
+ }
+
+ @NullMarked
+ private static class NonClosableOutputStream extends OutputStream {
+
+ private final OutputStream delegate;
+
+ NonClosableOutputStream(OutputStream delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ delegate.write(b);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ delegate.write(b, off, len);
+ }
+ }
}
diff --git a/java/src/test/java/io/cucumber/htmlformatter/MessagesToHtmlWriterTest.java b/java/src/test/java/io/cucumber/htmlformatter/MessagesToHtmlWriterTest.java
index fa69816c..2443310c 100644
--- a/java/src/test/java/io/cucumber/htmlformatter/MessagesToHtmlWriterTest.java
+++ b/java/src/test/java/io/cucumber/htmlformatter/MessagesToHtmlWriterTest.java
@@ -1,13 +1,13 @@
package io.cucumber.htmlformatter;
import io.cucumber.htmlformatter.MessagesToHtmlWriter.Serializer;
-import io.cucumber.messages.Convertor;
import io.cucumber.messages.types.Comment;
import io.cucumber.messages.types.Envelope;
import io.cucumber.messages.types.GherkinDocument;
import io.cucumber.messages.types.Location;
import io.cucumber.messages.types.TestRunFinished;
import io.cucumber.messages.types.TestRunStarted;
+import io.cucumber.messages.types.Timestamp;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayInputStream;
@@ -15,10 +15,10 @@
import java.io.IOException;
import java.time.Instant;
+import static io.cucumber.messages.Convertor.toMessage;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.singletonList;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.MatcherAssert.assertThat;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -43,75 +43,74 @@ private static String renderAsHtml(MessagesToHtmlWriter.Builder builder, Envelop
}
}
- return new String(bytes.toByteArray(), UTF_8);
+ return bytes.toString(UTF_8);
}
@Test
void it_writes_one_message_to_html() throws IOException {
Instant timestamp = Instant.ofEpochSecond(10);
- Envelope envelope = Envelope.of(new TestRunStarted(Convertor.toMessage(timestamp), null));
+ Envelope envelope = Envelope.of(new TestRunStarted(toMessage(timestamp), null));
String html = renderAsHtml(envelope);
- assertThat(html, containsString("" +
- "window.CUCUMBER_MESSAGES = [{\"testRunStarted\":{\"timestamp\":{\"seconds\":10,\"nanos\":0}}}];"));
+ assertThat(html).contains("window.CUCUMBER_MESSAGES = [{\"testRunStarted\":{\"timestamp\":{\"seconds\":10,\"nanos\":0}}}];");
}
@Test
void it_writes_no_message_to_html() throws IOException {
String html = renderAsHtml();
- assertThat(html, containsString("window.CUCUMBER_MESSAGES = [];"));
+ assertThat(html).contains("window.CUCUMBER_MESSAGES = [];");
}
@Test
void it_writes_default_title() throws IOException {
String html = renderAsHtml(MessagesToHtmlWriter.builder(serializer));
- assertThat(html, containsString("Cucumber"));
+ assertThat(html).contains("Cucumber");
}
@Test
void it_writes_custom_title() throws IOException {
String html = renderAsHtml(MessagesToHtmlWriter.builder(serializer).title("Custom Title"));
- assertThat(html, containsString("Custom Title"));
+ assertThat(html).contains("Custom Title");
}
@Test
void it_writes_default_icon() throws IOException {
String html = renderAsHtml(MessagesToHtmlWriter.builder(serializer));
- assertThat(html, containsString(""));
+ assertThat(html).contains("");
}
@Test
void it_writes_default_css() throws IOException {
String html = renderAsHtml(MessagesToHtmlWriter.builder(serializer)
.css(() -> createInputStream("p { color: red; }")));
- assertThat(html, containsString("p { color: red; }"));
+ assertThat(html).contains("p { color: red; }");
}
@Test
void it_writes_custom_css() throws IOException {
String html = renderAsHtml(MessagesToHtmlWriter.builder(serializer)
- .customCss(() -> createInputStream(("p { color: red; }"))));
- assertThat(html, containsString("p { color: red; }"));
+ .customCss(() -> createInputStream("p { color: red; }")));
+ assertThat(html).contains("p { color: red; }");
}
@Test
void it_writes_default_script() throws IOException {
String html = renderAsHtml(MessagesToHtmlWriter.builder(serializer)
- .script(() -> createInputStream(("console.log(\"Hello world\");"))));
- assertThat(html, containsString("console.log(\"Hello world\");"));
+ .script(() -> createInputStream("console.log(\"Hello world\");")));
+ assertThat(html).contains("console.log(\"Hello world\");");
}
@Test
void it_writes_custom_script() throws IOException {
String html = renderAsHtml(MessagesToHtmlWriter.builder(serializer)
- .customScript(() -> createInputStream(("console.log(\"Hello world\");"))));
- assertThat(html, containsString("console.log(\"Hello world\");"));
+ .customScript(() -> createInputStream("console.log(\"Hello world\");")));
+ assertThat(html).contains("console.log(\"Hello world\");");
}
@Test
@@ -119,7 +118,9 @@ void it_throws_when_writing_after_close() throws IOException {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
MessagesToHtmlWriter messagesToHtmlWriter = MessagesToHtmlWriter.builder(serializer).build(bytes);
messagesToHtmlWriter.close();
- assertThrows(IOException.class, () -> messagesToHtmlWriter.write(null));
+ assertThrows(IOException.class, () -> messagesToHtmlWriter.write(
+ Envelope.of(new TestRunStarted(new Timestamp(0L, 0), ""))
+ ));
}
@Test
@@ -148,14 +149,13 @@ public void close() throws IOException {
@Test
void it_writes_two_messages_separated_by_a_comma() throws IOException {
- Envelope testRunStarted = Envelope.of(new TestRunStarted(Convertor.toMessage(Instant.ofEpochSecond(10)), null));
+ Envelope testRunStarted = Envelope.of(new TestRunStarted(toMessage(Instant.ofEpochSecond(10)), null));
- Envelope envelope = Envelope.of(new TestRunFinished(null, true, Convertor.toMessage(Instant.ofEpochSecond(15)), null, null));
+ Envelope envelope = Envelope.of(new TestRunFinished(null, true, toMessage(Instant.ofEpochSecond(15)), null, null));
String html = renderAsHtml(testRunStarted, envelope);
- assertThat(html, containsString("" +
- "window.CUCUMBER_MESSAGES = [{\"testRunStarted\":{\"timestamp\":{\"seconds\":10,\"nanos\":0}}},{\"testRunFinished\":{\"success\":true,\"timestamp\":{\"seconds\":15,\"nanos\":0}}}];"));
+ assertThat(html).contains("window.CUCUMBER_MESSAGES = [{\"testRunStarted\":{\"timestamp\":{\"seconds\":10,\"nanos\":0}}},{\"testRunFinished\":{\"success\":true,\"timestamp\":{\"seconds\":15,\"nanos\":0}}}];");
}
@Test
@@ -164,12 +164,12 @@ void it_escapes_opening_angle_bracket() throws IOException {
null,
null,
singletonList(new Comment(
- new Location(0L, 0L),
+ new Location(0, 0),
""
))
));
String html = renderAsHtml(envelope);
- assertThat(html, containsString(
- "window.CUCUMBER_MESSAGES = [{\"gherkinDocument\":{\"comments\":[{\"location\":{\"line\":0,\"column\":0},\"text\":\"\\x3C/script>\\x3Cscript>alert('Hello')\\x3C/script>\"}]}}];"));
+ assertThat(html).contains(
+ "window.CUCUMBER_MESSAGES = [{\"gherkinDocument\":{\"comments\":[{\"location\":{\"line\":0,\"column\":0},\"text\":\"\\x3C/script>\\x3Cscript>alert('Hello')\\x3C/script>\"}]}}];");
}
}