From 20afb8107e1a6b375f8ec2dcb99972f041a0eca7 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 10 Aug 2025 15:54:44 +0200 Subject: [PATCH 01/29] Support customizations * Allows the title and icon in the html report to replaced with a custom title and icon. * Supports either replacing or extending the default javascript and css of the report. Closes: #403 --- Makefile | 11 +- .../Resources/.gitignore | 3 +- .../htmlformatter/MessagesToHtmlWriter.java | 254 +++++++++++++++--- .../io/cucumber/htmlformatter/.gitignore | 3 +- .../java/io/cucumber/htmlformatter/Main.java | 3 +- .../MessagesToHtmlWriterTest.java | 43 ++- javascript/logo.svg | 12 +- javascript/package.json | 2 +- javascript/src/CucumberHtmlStream.spec.ts | 3 +- javascript/src/CucumberHtmlStream.ts | 35 ++- javascript/src/icon.url | 1 + javascript/src/index.mustache.html | 10 +- ruby/assets/.gitignore | 3 +- 13 files changed, 317 insertions(+), 66 deletions(-) create mode 100644 javascript/src/icon.url diff --git a/Makefile b/Makefile index ed933929..a2edb234 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ javascript_source = $(wildcard javascript/src/*) -assets = main.css main.js main.js.LICENSE.txt index.mustache.html +assets = main.css main.js main.js.LICENSE.txt index.mustache.html icon.url ruby_assets = $(addprefix ruby/assets/,$(assets)) java_assets = $(addprefix java/src/main/resources/io/cucumber/htmlformatter/,$(assets)) dotnet_assets = $(addprefix dotnet/Cucumber.HtmlFormatter/Resources/,$(assets)) @@ -18,18 +18,27 @@ clean: ## Remove javascript built module and related artifacts from java and rub ruby/assets/index.mustache.html: javascript/src/index.mustache.html cp $< $@ +ruby/assets/icon.url: javascript/src/icon.url + cp $< $@ + ruby/assets/%: javascript/dist/% cp $< $@ java/src/main/resources/io/cucumber/htmlformatter/index.mustache.html: javascript/src/index.mustache.html cp $< $@ +java/src/main/resources/io/cucumber/htmlformatter/icon.url: javascript/src/icon.url + cp $< $@ + java/src/main/resources/io/cucumber/htmlformatter/%: javascript/dist/% cp $< $@ dotnet/Cucumber.HtmlFormatter/Resources/index.mustache.html: javascript/src/index.mustache.html cp $< $@ +dotnet/Cucumber.HtmlFormatter/Resources/icon.url: javascript/src/icon.url + cp $< $@ + dotnet/Cucumber.HtmlFormatter/Resources/%: javascript/dist/% cp $< $@ diff --git a/dotnet/Cucumber.HtmlFormatter/Resources/.gitignore b/dotnet/Cucumber.HtmlFormatter/Resources/.gitignore index 71cea849..6a3f7f6d 100644 --- a/dotnet/Cucumber.HtmlFormatter/Resources/.gitignore +++ b/dotnet/Cucumber.HtmlFormatter/Resources/.gitignore @@ -2,4 +2,5 @@ *.html *.js *.css -*.txt \ No newline at end of file +*.txt +*.url \ No newline at end of file diff --git a/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java b/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java index 8165dea5..1c18fb0e 100644 --- a/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java +++ b/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java @@ -4,6 +4,7 @@ import java.io.BufferedReader; import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -12,6 +13,7 @@ import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; +import java.util.function.Supplier; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; @@ -20,42 +22,139 @@ * Writes the message output of a test run as single page html report. */ public final class MessagesToHtmlWriter implements AutoCloseable { - private final String template; - private final Writer writer; + private final OutputStreamWriter writer; private final JsonInHtmlWriter jsonInHtmlWriter; private final Serializer serializer; + + private final String template; + private final Supplier title; + private final Supplier icon; + private final Supplier css; + private final Supplier customCss; + private final Supplier script; + private final Supplier customScript; + private boolean preMessageWritten = false; private boolean postMessageWritten = false; private boolean firstMessageWritten = false; private boolean streamClosed = false; + @Deprecated public MessagesToHtmlWriter(OutputStream outputStream, Serializer serializer) throws IOException { this( - new OutputStreamWriter( - requireNonNull(outputStream), - StandardCharsets.UTF_8), - requireNonNull(serializer) + createWriter(outputStream), + requireNonNull(serializer), + () -> new ByteArrayInputStream("Cucumber".getBytes(UTF_8)), + () -> getResource("icon.url"), + () -> getResource("main.css"), + MessagesToHtmlWriter::getEmptyResource, + () -> getResource("main.js"), + MessagesToHtmlWriter::getEmptyResource ); } - - private MessagesToHtmlWriter(Writer writer, Serializer serializer) throws IOException { + private MessagesToHtmlWriter( + OutputStreamWriter writer, + Serializer serializer, + Supplier title, + Supplier icon, + Supplier css, + Supplier customCss, + Supplier script, + Supplier customScript + ) { this.writer = writer; this.jsonInHtmlWriter = new JsonInHtmlWriter(writer); this.serializer = serializer; - this.template = readResource("index.mustache.html"); + this.template = readTemplate(); + this.title = title; + this.icon = icon; + this.css = css; + this.customCss = customCss; + this.customScript = customScript; + this.script = script; + } + + private static String readTemplate() { + try { + return readResource("index.mustache.html"); + } catch (IOException e) { + throw new RuntimeException("Could not read resource index.mustache.html", e); + } + } + + private static OutputStreamWriter createWriter(OutputStream outputStream) { + return new OutputStreamWriter( + requireNonNull(outputStream), + StandardCharsets.UTF_8); + } + + /** + * 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); + } + + private static ByteArrayInputStream getEmptyResource() { + return new ByteArrayInputStream(new byte[0]); + } + + private static void writeTemplateBetween(Writer writer, String template, String begin, String end) + throws IOException { + int beginIndex = begin == null ? 0 : template.indexOf(begin) + begin.length(); + int endIndex = end == null ? template.length() : template.indexOf(end); + writer.write(template.substring(beginIndex, endIndex)); + } + + private static void writeResource(Writer writer, Supplier resource) throws IOException { + writeResource(writer, resource.get()); + } + + private static void writeResource(Writer writer, InputStream resource) throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(resource, UTF_8)); + char[] buffer = new char[1024]; + for (int read = reader.read(buffer); read != -1; read = reader.read(buffer)) { + writer.write(buffer, 0, read); + } + } + + private static InputStream getResource(String name) { + InputStream resource = MessagesToHtmlWriter.class.getResourceAsStream(name); + requireNonNull(resource, name + " could not be loaded"); + return resource; + } + + private static String readResource(String name) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(baos, UTF_8))) { + InputStream resource = getResource(name); + writeResource(writer, resource); + } + return new String(baos.toByteArray(), UTF_8); } private void writePreMessage() throws IOException { - writeTemplateBetween(writer, template, null, "{{css}}"); - writeResource(writer, "main.css"); - writeTemplateBetween(writer, template, "{{css}}", "{{messages}}"); + 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}}", "{{customCss}}"); + writeResource(writer, customCss); + writeTemplateBetween(writer, template, "{{customCss}}", "{{messages}}"); } private void writePostMessage() throws IOException { writeTemplateBetween(writer, template, "{{messages}}", "{{script}}"); - writeResource(writer, "main.js"); - writeTemplateBetween(writer, template, "{{script}}", null); + writeResource(writer, script); + writeTemplateBetween(writer, template, "{{script}}", "{{customScript}}"); + writeResource(writer, customScript); + writeTemplateBetween(writer, template, "{{customScript}}", null); } /** @@ -113,31 +212,6 @@ public void close() throws IOException { } } - private static void writeTemplateBetween(Writer writer, String template, String begin, String end) - throws IOException { - int beginIndex = begin == null ? 0 : template.indexOf(begin) + begin.length(); - int endIndex = end == null ? template.length() : template.indexOf(end); - writer.write(template.substring(beginIndex, endIndex)); - } - - private static void writeResource(Writer writer, String name) throws IOException { - InputStream resource = MessagesToHtmlWriter.class.getResourceAsStream(name); - requireNonNull(resource, name + " could not be loaded"); - BufferedReader reader = new BufferedReader(new InputStreamReader(resource, UTF_8)); - char[] buffer = new char[1024]; - for (int read = reader.read(buffer); read != -1; read = reader.read(buffer)) { - writer.write(buffer, 0, read); - } - } - - private static String readResource(String name) throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(baos, UTF_8))) { - writeResource(writer, name); - } - return new String(baos.toByteArray(), UTF_8); - } - /** * Serializes a message to JSON. */ @@ -165,4 +239,106 @@ public interface Serializer { } + public static final class Builder { + private final Serializer serializer; + private Supplier title = () -> new ByteArrayInputStream("Cucumber".getBytes(UTF_8)); + private Supplier icon = () -> getResource("icon.url"); + private Supplier css = () -> getResource("main.css"); + private Supplier customCss = MessagesToHtmlWriter::getEmptyResource; + private Supplier script = () -> getResource("main.js"); + private Supplier customScript = MessagesToHtmlWriter::getEmptyResource; + + private Builder(Serializer serializer) { + this.serializer = requireNonNull(serializer); + } + + /** + * Sets a custom title for the report, default value "Cucumber". + * + * @param title the custom title. + * @return this builder + */ + public Builder title(String title) { + requireNonNull(title); + this.title = () -> new ByteArrayInputStream(title.getBytes(UTF_8)); + return this; + } + + /** + * Sets a custom icon for the report, default value the cucumber logo. + *

+ * The {@code icon} is any valid {@code href} value. + * + * @param icon the custom icon. + * @return this builder + */ + public Builder icon(Supplier icon) { + this.icon = requireNonNull(icon); + return this; + } + + /** + * Sets default css for the report. + *

+ * The default script styles the cucumber report. + * + * @param css the custom css. + * @return this builder + */ + public Builder css(Supplier css) { + this.css = requireNonNull(css); + return this; + } + + /** + * Sets custom css for the report. + *

+ * The custom css is applied after the default css. + * + * @param customCss the custom css. + * @return this builder + */ + public Builder customCss(Supplier customCss) { + this.customCss = requireNonNull(customCss); + return this; + } + + /** + * Replaces default script for the report. + *

+ * The default script renders the cucumber messages into a report. + * + * @param script the custom script. + * @return this builder + */ + public Builder script(Supplier script) { + this.script = requireNonNull(script); + return this; + } + + /** + * Sets custom script for the report. + *

+ * The custom script is applied after the default script. + * + * @param customScript the custom script. + * @return this builder + */ + public Builder customScript(Supplier customScript) { + this.customScript = requireNonNull(customScript); + return this; + } + + + /** + * Create an instance of the messages to html writer. + * + * @param out the output stream to write to + * @return a new instance of the messages to html writer. + */ + public MessagesToHtmlWriter build(OutputStream out) { + return new MessagesToHtmlWriter(createWriter(out), serializer, title, icon, css, customCss, script, customScript); + } + } + } diff --git a/java/src/main/resources/io/cucumber/htmlformatter/.gitignore b/java/src/main/resources/io/cucumber/htmlformatter/.gitignore index 71cea849..6a3f7f6d 100644 --- a/java/src/main/resources/io/cucumber/htmlformatter/.gitignore +++ b/java/src/main/resources/io/cucumber/htmlformatter/.gitignore @@ -2,4 +2,5 @@ *.html *.js *.css -*.txt \ No newline at end of file +*.txt +*.url \ No newline at end of file diff --git a/java/src/test/java/io/cucumber/htmlformatter/Main.java b/java/src/test/java/io/cucumber/htmlformatter/Main.java index 62cd675b..14b53eec 100644 --- a/java/src/test/java/io/cucumber/htmlformatter/Main.java +++ b/java/src/test/java/io/cucumber/htmlformatter/Main.java @@ -21,7 +21,8 @@ public static void main(String[] args) throws IOException { in = new FileInputStream(args[0]); } try (NdjsonToMessageIterable envelopes = new NdjsonToMessageIterable(in, deserializer)) { - try (MessagesToHtmlWriter htmlWriter = new MessagesToHtmlWriter(System.out, serializer)) { + MessagesToHtmlWriter.Builder builder = MessagesToHtmlWriter.builder(serializer); + try (MessagesToHtmlWriter htmlWriter = builder.build(System.out)) { for (Envelope envelope : envelopes) { htmlWriter.write(envelope); } diff --git a/java/src/test/java/io/cucumber/htmlformatter/MessagesToHtmlWriterTest.java b/java/src/test/java/io/cucumber/htmlformatter/MessagesToHtmlWriterTest.java index b4d1f1cc..01a00f41 100644 --- a/java/src/test/java/io/cucumber/htmlformatter/MessagesToHtmlWriterTest.java +++ b/java/src/test/java/io/cucumber/htmlformatter/MessagesToHtmlWriterTest.java @@ -4,18 +4,16 @@ import io.cucumber.messages.Convertor; import io.cucumber.messages.types.Comment; import io.cucumber.messages.types.Envelope; -import io.cucumber.messages.types.Feature; 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 org.junit.jupiter.api.Test; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.time.Instant; -import java.util.Arrays; -import java.util.Collections; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Collections.singletonList; @@ -44,11 +42,38 @@ void it_writes_no_message_to_html() throws IOException { assertThat(html, containsString("window.CUCUMBER_MESSAGES = [];")); } + @Test + void it_writes_custom_title() throws IOException { + String html = renderAsHtml(MessagesToHtmlWriter.builder(serializer).title("Custom Title")); + assertThat(html, containsString("Custom Title")); + } + + @Test + void it_writes_custom_icon() throws IOException { + String html = renderAsHtml(MessagesToHtmlWriter.builder(serializer) + .icon(() -> new ByteArrayInputStream("https://example.com/logo.svg".getBytes(UTF_8)))); + assertThat(html, containsString("")); + } + + + @Test + void it_writes_custom_css() throws IOException { + String html = renderAsHtml(MessagesToHtmlWriter.builder(serializer) + .customCss(() -> new ByteArrayInputStream(("p { color: red; }").getBytes(UTF_8)))); + assertThat(html, containsString("\t")); + } + + @Test + void it_writes_custom_script() throws IOException { + String html = renderAsHtml(MessagesToHtmlWriter.builder(serializer) + .customScript(() -> new ByteArrayInputStream(("console.log(\"Hello world\");").getBytes(UTF_8)))); + assertThat(html, containsString("")); + } @Test void it_throws_when_writing_after_close() throws IOException { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - MessagesToHtmlWriter messagesToHtmlWriter = new MessagesToHtmlWriter(bytes, serializer); + MessagesToHtmlWriter messagesToHtmlWriter = MessagesToHtmlWriter.builder(serializer).build(bytes); messagesToHtmlWriter.close(); assertThrows(IOException.class, () -> messagesToHtmlWriter.write(null)); } @@ -56,7 +81,7 @@ void it_throws_when_writing_after_close() throws IOException { @Test void it_can_be_closed_twice() throws IOException { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - MessagesToHtmlWriter messagesToHtmlWriter = new MessagesToHtmlWriter(bytes, serializer); + MessagesToHtmlWriter messagesToHtmlWriter = MessagesToHtmlWriter.builder(serializer).build(bytes); messagesToHtmlWriter.close(); assertDoesNotThrow(messagesToHtmlWriter::close); } @@ -69,7 +94,7 @@ public void close() throws IOException { throw new IOException("Can't close this"); } }; - MessagesToHtmlWriter messagesToHtmlWriter = new MessagesToHtmlWriter(bytes, serializer); + MessagesToHtmlWriter messagesToHtmlWriter = MessagesToHtmlWriter.builder(serializer).build(bytes); assertThrows(IOException.class, messagesToHtmlWriter::close); byte[] before = bytes.toByteArray(); assertDoesNotThrow(messagesToHtmlWriter::close); @@ -106,8 +131,12 @@ void it_escapes_forward_slashes() throws IOException { } private static String renderAsHtml(Envelope... messages) throws IOException { + return renderAsHtml(MessagesToHtmlWriter.builder(serializer), messages); + } + + private static String renderAsHtml(MessagesToHtmlWriter.Builder builder, Envelope... messages) throws IOException { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - try (MessagesToHtmlWriter messagesToHtmlWriter = new MessagesToHtmlWriter(bytes, serializer)) { + try (MessagesToHtmlWriter messagesToHtmlWriter = builder.build(bytes)) { for (Envelope message : messages) { messagesToHtmlWriter.write(message); } diff --git a/javascript/logo.svg b/javascript/logo.svg index 537ee11d..77509655 100644 --- a/javascript/logo.svg +++ b/javascript/logo.svg @@ -1,7 +1,9 @@ - - - - - + + + + + diff --git a/javascript/package.json b/javascript/package.json index 677d89c4..af0a5f30 100644 --- a/javascript/package.json +++ b/javascript/package.json @@ -15,7 +15,7 @@ "build:tsc": "tsc --build tsconfig.build.json", "build:webpack": "webpack", "build": "npm run clean && npm run build:tsc && npm run prepare && npm run build:webpack", - "prepare": "shx mkdir -p dist/src && shx cp src/*.scss dist/src && shx cp src/index.mustache.html dist/src", + "prepare": "shx mkdir -p dist/src && shx cp src/*.scss dist/src && shx cp src/index.mustache.html dist/src && shx cp src/icon.url dist/src", "test": "mocha", "prepublishOnly": "npm run build", "fix": "eslint --max-warnings 0 --fix src test && prettier --write src test", diff --git a/javascript/src/CucumberHtmlStream.spec.ts b/javascript/src/CucumberHtmlStream.spec.ts index bb9a953b..b60dd18c 100644 --- a/javascript/src/CucumberHtmlStream.spec.ts +++ b/javascript/src/CucumberHtmlStream.spec.ts @@ -22,7 +22,8 @@ async function renderAsHtml( sink.on('finish', () => resolve(html)) const cucumberHtmlStream = new CucumberHtmlStream( `${__dirname}/dummy.css`, - `${__dirname}/dummy.js` + `${__dirname}/dummy.js`, + `${__dirname}/icon.url`, ) cucumberHtmlStream.on('error', reject) cucumberHtmlStream.pipe(sink) diff --git a/javascript/src/CucumberHtmlStream.ts b/javascript/src/CucumberHtmlStream.ts index c85d7dc7..12b2fc4d 100644 --- a/javascript/src/CucumberHtmlStream.ts +++ b/javascript/src/CucumberHtmlStream.ts @@ -8,13 +8,16 @@ export class CucumberHtmlStream extends Transform { private preMessageWritten = false private postMessageWritten = false private firstMessageWritten = false + /** * @param cssPath * @param jsPath + * @param iconPath */ constructor( private readonly cssPath: string = path.join(__dirname, '..', 'main.css'), - private readonly jsPath: string = path.join(__dirname, '..', 'main.js') + private readonly jsPath: string = path.join(__dirname, '..', 'main.js'), + private readonly iconPath: string = path.join(__dirname, '..', 'icon.url'), ) { super({ objectMode: true }) } @@ -44,13 +47,30 @@ export class CucumberHtmlStream extends Transform { return callback() } this.preMessageWritten = true - this.writeTemplateBetween(null, '{{css}}', (err) => { + this.writeTemplateBetween(null, '{{title}}', (err) => { if (err) return callback(err) - this.writeFile(this.cssPath, (err) => { + this.push("Cucumber") + this.writeTemplateBetween('{{title}}', '{{icon}}', (err) => { if (err) return callback(err) - this.writeTemplateBetween('{{css}}', '{{messages}}', (err) => { + this.writeFile(this.iconPath, (err) => { if (err) return callback(err) - callback() + this.writeTemplateBetween('{{icon}}', '{{css}}', (err) => { + if (err) return callback(err) + this.writeFile(this.cssPath, (err) => { + if (err) return callback(err) + this.writeTemplateBetween('{{css}}', '{{customCss}}', (err) => { + if (err) return callback(err) + this.writeTemplateBetween( + '{{customCss}}', + '{{messages}}', + (err) => { + if (err) return callback(err) + callback() + } + ) + }) + }) + }) }) }) }) @@ -63,7 +83,10 @@ export class CucumberHtmlStream extends Transform { if (err) return callback(err) this.writeFile(this.jsPath, (err) => { if (err) return callback(err) - this.writeTemplateBetween('{{script}}', null, callback) + this.writeTemplateBetween('{{script}}', '{{customScript}}', (err) => { + if (err) return callback(err) + this.writeTemplateBetween('{{customScript}}', null, callback) + }) }) }) }) diff --git a/javascript/src/icon.url b/javascript/src/icon.url new file mode 100644 index 00000000..9d5009c8 --- /dev/null +++ b/javascript/src/icon.url @@ -0,0 +1 @@ +data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAuMDYgMC41NiAzMi41IDM3LjEzIj4KICAgIDxnIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPHBhdGggZD0iTS00LTFoNDB2NDBILTR6Ii8+CiAgICAgICAgPHBhdGggZmlsbD0iIzAwYTgxOCIKICAgICAgICAgICAgICBkPSJNMTYuNDM4LjU2M0M3LjM4Ni41NjMuMDYzIDcuODg2LjA2MyAxNi45MzhjMCA3Ljk2OCA1LjcxMiAxNC41ODkgMTMuMjUgMTYuMDYydjQuNjg4YzkuOC0xLjQ3OCAxOC40NzctOS4yNTcgMTkuMTI0LTE5LjQ3LjM5LTYuMTQ2LTIuNjc0LTEyLjQyMS03Ljg0My0xNS40NjhhMTMuNjIgMTMuNjIgMCAwIDAtMS44NzUtLjkzOGwtLjMxMy0uMTI1Yy0uMjg3LS4xMDYtLjU3Ny0uMjI1LS44NzUtLjMxMmExNi4yNDYgMTYuMjQ2IDAgMCAwLTUuMDkzLS44MTN2LjAwMXoiLz4KICAgICAgICA8cGF0aCBmaWxsPSIjZmZmIgogICAgICAgICAgICAgIGQ9Ik0xOS44MTMgNi42MjVhMS43ODcgMS43ODcgMCAwIDAtMS41NjMuNjI1Yy0uMy40LS40ODguNzg3LS42ODggMS4xODgtLjYgMS40LS40IDIuOS41IDQgMS40LS4zIDIuNTg4LTEuMTk0IDMuMTg4LTIuNTk0LjItLjQuMzEzLS45MTMuMzEzLTEuMzEzLjA2Mi0xLjA2Mi0uODE3LTEuODEtMS43NS0xLjkwNnptLTcuMjgyLjA5NGMtLjkxMy4wODctMS43ODEuODEyLTEuNzgxIDEuODEyIDAgLjQuMTEzLjkxMy4zMTMgMS4zMTMuNiAxLjQgMS44OCAyLjI5MyAzLjI4IDIuNTk0LjgtMS4xIDEuMDA3LTIuNi40MDctNC0uMi0uNC0uMzg3LS43OTQtLjY4OC0xLjA5NGExLjc1NyAxLjc1NyAwIDAgMC0xLjUzLS42MjVoLS4wMDF6TTcuNjI1IDExLjUzYy0xLjU3Ny4wODEtMi4yODEgMi4wNjMtLjk2OSAzLjA5NC40LjMuNzg4LjUxOSAxLjE4OC43MTkgMS40LjYgMy4wMTkuMzk0IDQuMjE4LS40MDYtLjMtMS4zLTEuMzE4LTIuNDk0LTIuNzE4LTMuMDk0LS41LS4yLS45MDYtLjMxMy0xLjQwNi0uMzEzLS4xMTMtLjAxMi0uMjA4LS4wMDUtLjMxMyAwem0xNS40MDYgNi4wNjNhNC41NzQgNC41NzQgMCAwIDAtMi41OTMuNzVjLjMgMS4zIDEuMzE4IDIuNDkzIDIuNzE4IDMuMDkzLjUuMi45MDcuMzEzIDEuNDA3LjMxMyAxLjguMSAyLjY4LTIuMTI1IDEuMjgtMy4xMjUtLjQtLjMtLjc4Ny0uNDg4LTEuMTg3LS42ODhhNC4zMiA0LjMyIDAgMCAwLTEuNjI1LS4zNDN6bS0xMy42NTYuMDkzYy0uNTUuMDExLTEuMS4xMi0xLjYyNS4zNDQtLjUuMi0uODg4LjQxOS0xLjE4OC43MTktMS4zIDEuMS0uNDI1IDMuMTk0IDEuMzc1IDMuMDk0LjUgMCAxLjAwNy0uMTEzIDEuNDA3LS4zMTMgMS40LS42IDIuMzk0LTEuNzkzIDIuNTk0LTMuMDkzYTQuNDc1IDQuNDc1IDAgMCAwLTIuNTYzLS43NXYtLjAwMXptNS4wNjMgMy4wNjNjLTEuNC4zLTIuNTg4IDEuMTk0LTMuMTg4IDIuNTk0LS4yLjQtLjMxMy44ODEtLjMxMyAxLjI4MS0uMSAxLjcgMi4yMiAyLjYxMyAzLjIyIDEuMzEzLjMtLjQuNDg3LS43ODguNjg3LTEuMTg4LjYtMS4zLjM5NC0yLjgtLjQwNi00em0zLjcxOC4wOTRjLS44IDEuMS0xLjAwNiAyLjYtLjQwNiA0IC4yLjQuMzg3Ljc5My42ODggMS4wOTMgMS4xIDEuMiAzLjQxMi4zMTMgMy4zMTItMS4xODcgMC0uNC0uMTEzLS45MTMtLjMxMy0xLjMxMy0uNi0xLjQtMS44OC0yLjI5My0zLjI4LTIuNTkzaC0uMDAxeiIvPgogICAgPC9nPgo8L3N2Zz4K \ No newline at end of file diff --git a/javascript/src/index.mustache.html b/javascript/src/index.mustache.html index 9005521d..cdd2fb6b 100644 --- a/javascript/src/index.mustache.html +++ b/javascript/src/index.mustache.html @@ -1,13 +1,16 @@ - Cucumber + {{title}} - + +

@@ -18,5 +21,8 @@ + \ No newline at end of file diff --git a/ruby/assets/.gitignore b/ruby/assets/.gitignore index 71cea849..6a3f7f6d 100644 --- a/ruby/assets/.gitignore +++ b/ruby/assets/.gitignore @@ -2,4 +2,5 @@ *.html *.js *.css -*.txt \ No newline at end of file +*.txt +*.url \ No newline at end of file From a91a76412820f7e5fed00c1cc7252f1b4e678968 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 10 Aug 2025 16:26:13 +0200 Subject: [PATCH 02/29] Harmonize with https://github.com/cucumber/html-formatter/pull/406 --- .../htmlformatter/MessagesToHtmlWriter.java | 84 +++++++------------ javascript/src/index.mustache.html | 4 +- 2 files changed, 34 insertions(+), 54 deletions(-) diff --git a/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java b/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java index 1c18fb0e..9ce579e8 100644 --- a/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java +++ b/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java @@ -29,9 +29,9 @@ public final class MessagesToHtmlWriter implements AutoCloseable { private final String template; private final Supplier title; private final Supplier icon; - private final Supplier css; + private final Supplier css = () -> getResource("main.css"); private final Supplier customCss; - private final Supplier script; + private final Supplier script = () -> getResource("main.js"); private final Supplier customScript; private boolean preMessageWritten = false; @@ -44,12 +44,10 @@ public MessagesToHtmlWriter(OutputStream outputStream, Serializer serializer) th this( createWriter(outputStream), requireNonNull(serializer), - () -> new ByteArrayInputStream("Cucumber".getBytes(UTF_8)), + () -> createInputStream("Cucumber"), () -> getResource("icon.url"), - () -> getResource("main.css"), - MessagesToHtmlWriter::getEmptyResource, - () -> getResource("main.js"), - MessagesToHtmlWriter::getEmptyResource + MessagesToHtmlWriter::createEmptyInputStream, + MessagesToHtmlWriter::createEmptyInputStream ); } @@ -58,9 +56,7 @@ private MessagesToHtmlWriter( Serializer serializer, Supplier title, Supplier icon, - Supplier css, Supplier customCss, - Supplier script, Supplier customScript ) { this.writer = writer; @@ -69,15 +65,18 @@ private MessagesToHtmlWriter( this.template = readTemplate(); this.title = title; this.icon = icon; - this.css = css; this.customCss = customCss; this.customScript = customScript; - this.script = script; } private static String readTemplate() { try { - return readResource("index.mustache.html"); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(baos, UTF_8))) { + InputStream resource = getResource("index.mustache.html"); + writeResource(writer, resource); + } + return new String(baos.toByteArray(), UTF_8); } catch (IOException e) { throw new RuntimeException("Could not read resource index.mustache.html", e); } @@ -98,8 +97,12 @@ private static OutputStreamWriter createWriter(OutputStream outputStream) { public static Builder builder(Serializer serializer) { return new Builder(serializer); } - - private static ByteArrayInputStream getEmptyResource() { + + private static InputStream createInputStream(String text) { + return new ByteArrayInputStream(text.getBytes(UTF_8)); + } + + private static InputStream createEmptyInputStream() { return new ByteArrayInputStream(new byte[0]); } @@ -128,15 +131,6 @@ private static InputStream getResource(String name) { return resource; } - private static String readResource(String name) throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(baos, UTF_8))) { - InputStream resource = getResource(name); - writeResource(writer, resource); - } - return new String(baos.toByteArray(), UTF_8); - } - private void writePreMessage() throws IOException { writeTemplateBetween(writer, template, null, "{{title}}"); writeResource(writer, title); @@ -144,17 +138,17 @@ private void writePreMessage() throws IOException { writeResource(writer, icon); writeTemplateBetween(writer, template, "{{icon}}", "{{css}}"); writeResource(writer, css); - writeTemplateBetween(writer, template, "{{css}}", "{{customCss}}"); + writeTemplateBetween(writer, template, "{{css}}", "{{custom_css}}"); writeResource(writer, customCss); - writeTemplateBetween(writer, template, "{{customCss}}", "{{messages}}"); + writeTemplateBetween(writer, template, "{{custom_css}}", "{{messages}}"); } private void writePostMessage() throws IOException { writeTemplateBetween(writer, template, "{{messages}}", "{{script}}"); writeResource(writer, script); - writeTemplateBetween(writer, template, "{{script}}", "{{customScript}}"); + writeTemplateBetween(writer, template, "{{script}}", "{{custom_script}}"); writeResource(writer, customScript); - writeTemplateBetween(writer, template, "{{customScript}}", null); + writeTemplateBetween(writer, template, "{{custom_script}}", null); } /** @@ -241,12 +235,10 @@ public interface Serializer { public static final class Builder { private final Serializer serializer; - private Supplier title = () -> new ByteArrayInputStream("Cucumber".getBytes(UTF_8)); + private Supplier title = () -> createInputStream("Cucumber"); private Supplier icon = () -> getResource("icon.url"); - private Supplier css = () -> getResource("main.css"); - private Supplier customCss = MessagesToHtmlWriter::getEmptyResource; - private Supplier script = () -> getResource("main.js"); - private Supplier customScript = MessagesToHtmlWriter::getEmptyResource; + private Supplier customCss = MessagesToHtmlWriter::createEmptyInputStream; + private Supplier customScript = MessagesToHtmlWriter::createEmptyInputStream; private Builder(Serializer serializer) { this.serializer = requireNonNull(serializer); @@ -260,7 +252,7 @@ private Builder(Serializer serializer) { */ public Builder title(String title) { requireNonNull(title); - this.title = () -> new ByteArrayInputStream(title.getBytes(UTF_8)); + this.title = () -> createInputStream(title); return this; } @@ -278,15 +270,16 @@ public Builder icon(Supplier icon) { } /** - * Sets default css for the report. + * Sets a custom icon for the report, default value the cucumber logo. *

- * The default script styles the cucumber report. + * The {@code icon} is any valid {@code href} value. * - * @param css the custom css. + * @param icon the custom icon. * @return this builder */ - public Builder css(Supplier css) { - this.css = requireNonNull(css); + public Builder icon(String icon) { + requireNonNull(icon); + this.icon = () -> createInputStream(icon); return this; } @@ -303,19 +296,6 @@ public Builder customCss(Supplier customCss) { return this; } - /** - * Replaces default script for the report. - *

- * The default script renders the cucumber messages into a report. - * - * @param script the custom script. - * @return this builder - */ - public Builder script(Supplier script) { - this.script = requireNonNull(script); - return this; - } - /** * Sets custom script for the report. *

@@ -337,7 +317,7 @@ public Builder customScript(Supplier customScript) { * @return a new instance of the messages to html writer. */ public MessagesToHtmlWriter build(OutputStream out) { - return new MessagesToHtmlWriter(createWriter(out), serializer, title, icon, css, customCss, script, customScript); + return new MessagesToHtmlWriter(createWriter(out), serializer, title, icon, customCss, customScript); } } diff --git a/javascript/src/index.mustache.html b/javascript/src/index.mustache.html index cdd2fb6b..88f49ec3 100644 --- a/javascript/src/index.mustache.html +++ b/javascript/src/index.mustache.html @@ -9,7 +9,7 @@ {{css}} @@ -22,7 +22,7 @@ {{script}} \ No newline at end of file From fa4aca29227ddf166ccea3ed49ca474c00dc9da5 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 10 Aug 2025 16:39:01 +0200 Subject: [PATCH 03/29] Fix platform dependent eols --- .../io/cucumber/htmlformatter/MessagesToHtmlWriterTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/java/src/test/java/io/cucumber/htmlformatter/MessagesToHtmlWriterTest.java b/java/src/test/java/io/cucumber/htmlformatter/MessagesToHtmlWriterTest.java index 01a00f41..5bc9ce4c 100644 --- a/java/src/test/java/io/cucumber/htmlformatter/MessagesToHtmlWriterTest.java +++ b/java/src/test/java/io/cucumber/htmlformatter/MessagesToHtmlWriterTest.java @@ -19,6 +19,7 @@ import static java.util.Collections.singletonList; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.text.IsEqualCompressingWhiteSpace.equalToCompressingWhiteSpace; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -60,14 +61,14 @@ void it_writes_custom_icon() throws IOException { void it_writes_custom_css() throws IOException { String html = renderAsHtml(MessagesToHtmlWriter.builder(serializer) .customCss(() -> new ByteArrayInputStream(("p { color: red; }").getBytes(UTF_8)))); - assertThat(html, containsString("\t")); + assertThat(html, equalToCompressingWhiteSpace("\t")); } @Test void it_writes_custom_script() throws IOException { String html = renderAsHtml(MessagesToHtmlWriter.builder(serializer) .customScript(() -> new ByteArrayInputStream(("console.log(\"Hello world\");").getBytes(UTF_8)))); - assertThat(html, containsString("")); + assertThat(html, equalToCompressingWhiteSpace("")); } @Test From 18f51406338f02fc4c7fb11d99dc611bd13f7688 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 10 Aug 2025 16:40:22 +0200 Subject: [PATCH 04/29] Run lint fix --- javascript/src/CucumberHtmlStream.spec.ts | 2 +- javascript/src/CucumberHtmlStream.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/javascript/src/CucumberHtmlStream.spec.ts b/javascript/src/CucumberHtmlStream.spec.ts index b60dd18c..eeffc3b7 100644 --- a/javascript/src/CucumberHtmlStream.spec.ts +++ b/javascript/src/CucumberHtmlStream.spec.ts @@ -23,7 +23,7 @@ async function renderAsHtml( const cucumberHtmlStream = new CucumberHtmlStream( `${__dirname}/dummy.css`, `${__dirname}/dummy.js`, - `${__dirname}/icon.url`, + `${__dirname}/icon.url` ) cucumberHtmlStream.on('error', reject) cucumberHtmlStream.pipe(sink) diff --git a/javascript/src/CucumberHtmlStream.ts b/javascript/src/CucumberHtmlStream.ts index 12b2fc4d..20dce1f7 100644 --- a/javascript/src/CucumberHtmlStream.ts +++ b/javascript/src/CucumberHtmlStream.ts @@ -17,7 +17,7 @@ export class CucumberHtmlStream extends Transform { constructor( private readonly cssPath: string = path.join(__dirname, '..', 'main.css'), private readonly jsPath: string = path.join(__dirname, '..', 'main.js'), - private readonly iconPath: string = path.join(__dirname, '..', 'icon.url'), + private readonly iconPath: string = path.join(__dirname, '..', 'icon.url') ) { super({ objectMode: true }) } @@ -49,7 +49,7 @@ export class CucumberHtmlStream extends Transform { this.preMessageWritten = true this.writeTemplateBetween(null, '{{title}}', (err) => { if (err) return callback(err) - this.push("Cucumber") + this.push('Cucumber') this.writeTemplateBetween('{{title}}', '{{icon}}', (err) => { if (err) return callback(err) this.writeFile(this.iconPath, (err) => { From 337eabe47c1916e97f529eb455984a2b1b2b40c7 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 10 Aug 2025 17:20:54 +0200 Subject: [PATCH 05/29] Fix more --- .../MessagesToHtmlWriterTest.java | 21 +++++++++--- .../cucumber/html_formatter/assets_loader.rb | 4 +++ ruby/lib/cucumber/html_formatter/formatter.rb | 14 ++++++-- .../cucumber/html_formatter/formatter_spec.rb | 33 +++++++++++-------- 4 files changed, 51 insertions(+), 21 deletions(-) diff --git a/java/src/test/java/io/cucumber/htmlformatter/MessagesToHtmlWriterTest.java b/java/src/test/java/io/cucumber/htmlformatter/MessagesToHtmlWriterTest.java index 5bc9ce4c..10a3cb68 100644 --- a/java/src/test/java/io/cucumber/htmlformatter/MessagesToHtmlWriterTest.java +++ b/java/src/test/java/io/cucumber/htmlformatter/MessagesToHtmlWriterTest.java @@ -19,7 +19,6 @@ import static java.util.Collections.singletonList; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.text.IsEqualCompressingWhiteSpace.equalToCompressingWhiteSpace; 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,32 +42,44 @@ void it_writes_no_message_to_html() throws IOException { assertThat(html, containsString("window.CUCUMBER_MESSAGES = [];")); } + @Test + void it_writes_default_title() throws IOException { + String html = renderAsHtml(MessagesToHtmlWriter.builder(serializer)); + assertThat(html, containsString("Cucumber")); + } + @Test void it_writes_custom_title() throws IOException { String html = renderAsHtml(MessagesToHtmlWriter.builder(serializer).title("Custom Title")); assertThat(html, containsString("Custom Title")); } + @Test + void it_writes_default_icon() throws IOException { + String html = renderAsHtml(MessagesToHtmlWriter.builder(serializer)); + assertThat(html, containsString(" new ByteArrayInputStream("https://example.com/logo.svg".getBytes(UTF_8)))); assertThat(html, containsString("")); } - @Test void it_writes_custom_css() throws IOException { String html = renderAsHtml(MessagesToHtmlWriter.builder(serializer) .customCss(() -> new ByteArrayInputStream(("p { color: red; }").getBytes(UTF_8)))); - assertThat(html, equalToCompressingWhiteSpace("\t")); + assertThat(html, containsString("p { color: red; }")); } @Test void it_writes_custom_script() throws IOException { String html = renderAsHtml(MessagesToHtmlWriter.builder(serializer) .customScript(() -> new ByteArrayInputStream(("console.log(\"Hello world\");").getBytes(UTF_8)))); - assertThat(html, equalToCompressingWhiteSpace("")); + assertThat(html, containsString("console.log(\"Hello world\");")); } @Test @@ -88,7 +99,7 @@ void it_can_be_closed_twice() throws IOException { } @Test - void it_is_idempotent_under_failure_to_close() throws IOException { + void it_is_idempotent_under_failure_to_close() { ByteArrayOutputStream bytes = new ByteArrayOutputStream() { @Override public void close() throws IOException { diff --git a/ruby/lib/cucumber/html_formatter/assets_loader.rb b/ruby/lib/cucumber/html_formatter/assets_loader.rb index eb60d948..ac71f4af 100644 --- a/ruby/lib/cucumber/html_formatter/assets_loader.rb +++ b/ruby/lib/cucumber/html_formatter/assets_loader.rb @@ -8,6 +8,10 @@ def template read_asset('index.mustache.html') end + def icon + read_asset('icon.url') + end + def css read_asset('main.css') end diff --git a/ruby/lib/cucumber/html_formatter/formatter.rb b/ruby/lib/cucumber/html_formatter/formatter.rb index 55ad0ee8..f954735f 100644 --- a/ruby/lib/cucumber/html_formatter/formatter.rb +++ b/ruby/lib/cucumber/html_formatter/formatter.rb @@ -43,9 +43,15 @@ def write_post_message def pre_message [ - template_writer.write_between(nil, '{{css}}'), + template_writer.write_between(nil, '{{title}}'), + 'Cucumber', + template_writer.write_between('{{title}}', '{{icon}}'), + AssetsLoader.icon, + template_writer.write_between('{{icon}}', '{{css}}'), AssetsLoader.css, - template_writer.write_between('{{css}}', '{{messages}}') + template_writer.write_between('{{css}}', '{{custom_css}}'), + '', + template_writer.write_between('{{custom_css}}', '{{messages}}') ].join("\n") end @@ -53,7 +59,9 @@ def post_message [ template_writer.write_between('{{messages}}', '{{script}}'), AssetsLoader.script, - template_writer.write_between('{{script}}', nil) + template_writer.write_between('{{script}}', '{{custom_script}}'), + '', + template_writer.write_between('{{custom_script}}', nil) ].join("\n") end diff --git a/ruby/spec/cucumber/html_formatter/formatter_spec.rb b/ruby/spec/cucumber/html_formatter/formatter_spec.rb index 655b3159..375f6ccc 100644 --- a/ruby/spec/cucumber/html_formatter/formatter_spec.rb +++ b/ruby/spec/cucumber/html_formatter/formatter_spec.rb @@ -9,22 +9,29 @@ before do allow(Cucumber::HTMLFormatter::AssetsLoader) .to receive_messages( - template: '{{css}}{{messages}}{{script}}', - css: '', - script: "" + template: "{{title}}{{icon}}{{css}}{{custom_css}}{{messages}}{{script}}{{custom_script}}", + icon: 'https://example.org/icon.svg', + css: 'div { color: red }', + script: "alert('Hi');" ) end describe '#process_messages' do let(:message) { Cucumber::Messages::Envelope.new(pickle: Cucumber::Messages::Pickle.new(id: 'some-random-uid')) } let(:expected_report) do - <<~REPORT.strip - - - - #{message.to_json} - - + <<~REPORT + + Cucumber + + https://example.org/icon.svg + + div { color: red } + + + #{message.to_json} + alert('Hi'); + + REPORT end @@ -39,14 +46,14 @@ it 'outputs the content of the template up to {{messages}}' do formatter.write_pre_message - expect(out.string).to eq("\n\n\n") + expect(out.string).to eq("\nCucumber\n\nhttps://example.org/icon.svg\n\ndiv { color: red }\n\n\n") end it 'does not write the content twice' do formatter.write_pre_message formatter.write_pre_message - expect(out.string).to eq("\n\n\n") + expect(out.string).to eq("\nCucumber\n\nhttps://example.org/icon.svg\n\ndiv { color: red }\n\n\n") end end @@ -90,7 +97,7 @@ it 'outputs the template end' do formatter.write_post_message - expect(out.string).to eq("\n\n") + expect(out.string).to eq("\nalert('Hi');\n\n\n") end end end From cc345c19b9c9e41bdd1f318da9ca8ff2c86a0567 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 10 Aug 2025 17:24:56 +0200 Subject: [PATCH 06/29] Fix more --- javascript/test/acceptance.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/javascript/test/acceptance.spec.ts b/javascript/test/acceptance.spec.ts index 9da40afc..d1810a42 100644 --- a/javascript/test/acceptance.spec.ts +++ b/javascript/test/acceptance.spec.ts @@ -24,7 +24,8 @@ test.beforeAll(async () => { new NdjsonToMessageStream(), new CucumberHtmlStream( path.join(__dirname, '../dist/main.css'), - path.join(__dirname, '../dist/main.js') + path.join(__dirname, '../dist/main.js'), + path.join(__dirname, '../dist/icon.url') ), fs.createWriteStream(outputFile) ) From 7bb0e6aa41ddcf69fecd23bf439fc036a8adeee6 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 10 Aug 2025 17:35:32 +0200 Subject: [PATCH 07/29] Fix whitespace issues --- .../Cucumber.HtmlFormatter/Resources/.gitignore | 2 +- .../io/cucumber/htmlformatter/.gitignore | 2 +- javascript/logo.svg | 9 --------- javascript/src/index.mustache.html | 2 +- ruby/assets/.gitignore | 2 +- ruby/lib/cucumber/html_formatter/formatter.rb | 6 ++---- .../cucumber/html_formatter/formatter_spec.rb | 15 +++++---------- 7 files changed, 11 insertions(+), 27 deletions(-) delete mode 100644 javascript/logo.svg diff --git a/dotnet/Cucumber.HtmlFormatter/Resources/.gitignore b/dotnet/Cucumber.HtmlFormatter/Resources/.gitignore index 6a3f7f6d..ad310592 100644 --- a/dotnet/Cucumber.HtmlFormatter/Resources/.gitignore +++ b/dotnet/Cucumber.HtmlFormatter/Resources/.gitignore @@ -3,4 +3,4 @@ *.js *.css *.txt -*.url \ No newline at end of file +*.url diff --git a/java/src/main/resources/io/cucumber/htmlformatter/.gitignore b/java/src/main/resources/io/cucumber/htmlformatter/.gitignore index 6a3f7f6d..ad310592 100644 --- a/java/src/main/resources/io/cucumber/htmlformatter/.gitignore +++ b/java/src/main/resources/io/cucumber/htmlformatter/.gitignore @@ -3,4 +3,4 @@ *.js *.css *.txt -*.url \ No newline at end of file +*.url diff --git a/javascript/logo.svg b/javascript/logo.svg deleted file mode 100644 index 77509655..00000000 --- a/javascript/logo.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - diff --git a/javascript/src/index.mustache.html b/javascript/src/index.mustache.html index 88f49ec3..80572ab6 100644 --- a/javascript/src/index.mustache.html +++ b/javascript/src/index.mustache.html @@ -25,4 +25,4 @@ {{custom_script}} - \ No newline at end of file + diff --git a/ruby/assets/.gitignore b/ruby/assets/.gitignore index 6a3f7f6d..ad310592 100644 --- a/ruby/assets/.gitignore +++ b/ruby/assets/.gitignore @@ -3,4 +3,4 @@ *.js *.css *.txt -*.url \ No newline at end of file +*.url diff --git a/ruby/lib/cucumber/html_formatter/formatter.rb b/ruby/lib/cucumber/html_formatter/formatter.rb index f954735f..601aac7b 100644 --- a/ruby/lib/cucumber/html_formatter/formatter.rb +++ b/ruby/lib/cucumber/html_formatter/formatter.rb @@ -50,9 +50,8 @@ def pre_message template_writer.write_between('{{icon}}', '{{css}}'), AssetsLoader.css, template_writer.write_between('{{css}}', '{{custom_css}}'), - '', template_writer.write_between('{{custom_css}}', '{{messages}}') - ].join("\n") + ].join("") end def post_message @@ -60,9 +59,8 @@ def post_message template_writer.write_between('{{messages}}', '{{script}}'), AssetsLoader.script, template_writer.write_between('{{script}}', '{{custom_script}}'), - '', template_writer.write_between('{{custom_script}}', nil) - ].join("\n") + ].join("") end def template_writer diff --git a/ruby/spec/cucumber/html_formatter/formatter_spec.rb b/ruby/spec/cucumber/html_formatter/formatter_spec.rb index 375f6ccc..bc6072ff 100644 --- a/ruby/spec/cucumber/html_formatter/formatter_spec.rb +++ b/ruby/spec/cucumber/html_formatter/formatter_spec.rb @@ -9,7 +9,7 @@ before do allow(Cucumber::HTMLFormatter::AssetsLoader) .to receive_messages( - template: "{{title}}{{icon}}{{css}}{{custom_css}}{{messages}}{{script}}{{custom_script}}", + template: "{{title}}\n{{icon}}\n{{css}}\n{{custom_css}}\n{{messages}}\n{{script}}\n{{custom_script}}\n", icon: 'https://example.org/icon.svg', css: 'div { color: red }', script: "alert('Hi');" @@ -19,19 +19,14 @@ describe '#process_messages' do let(:message) { Cucumber::Messages::Envelope.new(pickle: Cucumber::Messages::Pickle.new(id: 'some-random-uid')) } let(:expected_report) do - <<~REPORT - + <<~REPORT.lstrip Cucumber - https://example.org/icon.svg - div { color: red } - #{message.to_json} alert('Hi'); - REPORT end @@ -46,14 +41,14 @@ it 'outputs the content of the template up to {{messages}}' do formatter.write_pre_message - expect(out.string).to eq("\nCucumber\n\nhttps://example.org/icon.svg\n\ndiv { color: red }\n\n\n") + expect(out.string).to eq("Cucumber\nhttps://example.org/icon.svg\ndiv { color: red }\n\n") end it 'does not write the content twice' do formatter.write_pre_message formatter.write_pre_message - expect(out.string).to eq("\nCucumber\n\nhttps://example.org/icon.svg\n\ndiv { color: red }\n\n\n") + expect(out.string).to eq("Cucumber\nhttps://example.org/icon.svg\ndiv { color: red }\n\n") end end @@ -97,7 +92,7 @@ it 'outputs the template end' do formatter.write_post_message - expect(out.string).to eq("\nalert('Hi');\n\n\n") + expect(out.string).to eq("\nalert('Hi');\n\n") end end end From 945bb3a842cace11386e8934ef23c3b26537554f Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 10 Aug 2025 17:55:11 +0200 Subject: [PATCH 08/29] Fix paths for js --- javascript/test/acceptance.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/test/acceptance.spec.ts b/javascript/test/acceptance.spec.ts index d1810a42..a5598e8a 100644 --- a/javascript/test/acceptance.spec.ts +++ b/javascript/test/acceptance.spec.ts @@ -25,7 +25,7 @@ test.beforeAll(async () => { new CucumberHtmlStream( path.join(__dirname, '../dist/main.css'), path.join(__dirname, '../dist/main.js'), - path.join(__dirname, '../dist/icon.url') + path.join(__dirname, '../dist/src/icon.url') ), fs.createWriteStream(outputFile) ) From 87e6565fd91c76c9baa32979fc86ec2403854485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1sp=C3=A1r=20Nagy?= Date: Mon, 11 Aug 2025 10:06:53 +0200 Subject: [PATCH 09/29] Implement customizations and builder, converted to idiomatic .NET "settings object" pattern --- .../HtmlReportSettings.cs | 48 +++++++++ .../MessagesToHtmlWriter.cs | 57 ++++++---- .../MessagesToHtmlWriterTest.cs | 102 +++++++++++------- 3 files changed, 150 insertions(+), 57 deletions(-) create mode 100644 dotnet/Cucumber.HtmlFormatter/HtmlReportSettings.cs diff --git a/dotnet/Cucumber.HtmlFormatter/HtmlReportSettings.cs b/dotnet/Cucumber.HtmlFormatter/HtmlReportSettings.cs new file mode 100644 index 00000000..435672d3 --- /dev/null +++ b/dotnet/Cucumber.HtmlFormatter/HtmlReportSettings.cs @@ -0,0 +1,48 @@ +namespace Cucumber.HtmlFormatter; + +///

+/// Settings for HTML report generation +/// +public class HtmlReportSettings +{ + private const string DEFAULT_TITLE = "Cucumber"; + private string? _icon = null; + + /// + /// Gets or sets the title of the HTML report. + /// Default is "Cucumber". + /// + public string Title { get; set; } = DEFAULT_TITLE; + + /// + /// Gets or sets the icon for the HTML report. + /// Default is the Cucumber icon as a base64-encoded SVG. + /// + public string Icon + { + get => _icon ?? LoadDefaultIcon(); + set => _icon = value; + } + + /// + /// Gets or sets custom CSS to include in the HTML report. + /// Default is empty. + /// + public string CustomCss { get; set; } = string.Empty; + + /// + /// Gets or sets custom Javascript to include after the main Javascript section of the HTML report. + /// Default is empty. + /// + public string CustomScript { get; set; } = string.Empty; + + /// + /// Creates a new instance of HtmlReportSettings with default values. + /// + public HtmlReportSettings() + { + } + + private static string LoadDefaultIcon() + => MessagesToHtmlWriter.GetResource("icon.url"); +} \ No newline at end of file diff --git a/dotnet/Cucumber.HtmlFormatter/MessagesToHtmlWriter.cs b/dotnet/Cucumber.HtmlFormatter/MessagesToHtmlWriter.cs index 7c460b22..b0d52d2f 100644 --- a/dotnet/Cucumber.HtmlFormatter/MessagesToHtmlWriter.cs +++ b/dotnet/Cucumber.HtmlFormatter/MessagesToHtmlWriter.cs @@ -9,6 +9,7 @@ public class MessagesToHtmlWriter : IDisposable private readonly Action _streamSerializer; private readonly string _template; private readonly JsonInHtmlWriter _jsonInHtmlWriter; + private readonly HtmlReportSettings _settings; private bool _streamClosed = false; private bool _preMessageWritten = false; private bool _firstMessageWritten = false; @@ -16,18 +17,20 @@ public class MessagesToHtmlWriter : IDisposable private readonly bool _isAsyncInitialized = false; [Obsolete("Cucumber.HtmlFormatter moving to async only operations. Please use the MessagesToHtmlWriter(Stream, Func) constructor", false)] - public MessagesToHtmlWriter(Stream stream, Action streamSerializer) : this(new StreamWriter(stream), streamSerializer) - { - } - public MessagesToHtmlWriter(Stream stream, Func asyncStreamSerializer) : this(new StreamWriter(stream), asyncStreamSerializer) { } + public MessagesToHtmlWriter(Stream stream, Action streamSerializer) + : this(new StreamWriter(stream), streamSerializer) { } + + public MessagesToHtmlWriter(Stream stream, Func asyncStreamSerializer, HtmlReportSettings? settings = null) + : this(new StreamWriter(stream), asyncStreamSerializer, settings) { } [Obsolete("Cucumber.HtmlFormatter moving to async only operations. Please use the MessagesToHtmlWriter(StreamWriter, Func) constructor", false)] public MessagesToHtmlWriter(StreamWriter writer, Action streamSerializer) { - this._writer = writer; - this._streamSerializer = streamSerializer; + _writer = writer; + _streamSerializer = streamSerializer; + _settings = new HtmlReportSettings(); // Create async wrapper for sync serializer - this._asyncStreamSerializer = (w, e) => + _asyncStreamSerializer = (w, e) => { streamSerializer(w, e); return Task.CompletedTask; @@ -36,12 +39,14 @@ public MessagesToHtmlWriter(StreamWriter writer, Action _jsonInHtmlWriter = new JsonInHtmlWriter(writer); _isAsyncInitialized = false; } - public MessagesToHtmlWriter(StreamWriter writer, Func asyncStreamSerializer) + + public MessagesToHtmlWriter(StreamWriter writer, Func asyncStreamSerializer, HtmlReportSettings? settings = null) { - this._writer = writer; - this._asyncStreamSerializer = asyncStreamSerializer; + _writer = writer; + _asyncStreamSerializer = asyncStreamSerializer; + _settings = settings ?? new(); // Create sync wrapper for async serializer (will block) - this._streamSerializer = (w, e) => asyncStreamSerializer(w, e).GetAwaiter().GetResult(); + _streamSerializer = (w, e) => asyncStreamSerializer(w, e).GetAwaiter().GetResult(); _template = GetResource("index.mustache.html"); _jsonInHtmlWriter = new JsonInHtmlWriter(writer); _isAsyncInitialized = true; @@ -49,30 +54,46 @@ public MessagesToHtmlWriter(StreamWriter writer, FuncCucumber"); + } + + [TestMethod] + public async Task ItWritesCustomTitle() + { + string html = await RenderAsHtmlAsync(new HtmlReportSettings { Title = "Custom Title" }); + StringAssert.Contains(html, "Custom Title"); + } + + [TestMethod] + public async Task ItWritesDefaultIcon() + { + string html = await RenderAsHtmlAsync(); + StringAssert.Contains(html, "" }); + StringAssert.Contains(html, ""); + } + + [TestMethod] + public async Task ItWritesCustomCss() { - string html = RenderAsHtml(); - Assert.IsTrue(html.Contains("window.CUCUMBER_MESSAGES = [];")); + string html = await RenderAsHtmlAsync(new HtmlReportSettings { CustomCss = "p { color: red; }" }); + StringAssert.Contains(html, "p { color: red; }"); + } + + [TestMethod] + public async Task ItWritesCustomScript() + { + string html = await RenderAsHtmlAsync(new HtmlReportSettings() { CustomScript = "console.log(\"Hello world\");" }); + StringAssert.Contains(html, "console.log(\"Hello world\");"); } [TestMethod] public async Task ItWritesNoMessageToHtmlAsync() { string html = await RenderAsHtmlAsync(); - Assert.IsTrue(html.Contains("window.CUCUMBER_MESSAGES = [];")); + StringAssert.Contains(html, "window.CUCUMBER_MESSAGES = [];"); } [TestMethod] @@ -153,37 +196,13 @@ public async Task ItIsIdempotentUnderFailureToCloseAsync() CollectionAssert.AreEqual(before, after); } - - [TestMethod] - public void ItWritesTwoMessagesSeparatedByAComma() - { - Envelope testRunStarted = Envelope.Create(new TestRunStarted(Converters.ToTimestamp(DateTime.UnixEpoch.AddSeconds(10).ToUniversalTime()), null)); - Envelope envelope = Envelope.Create(new TestRunFinished(null, true, Converters.ToTimestamp(DateTime.UnixEpoch.AddSeconds(15).ToUniversalTime()), null, null)); - string html = RenderAsHtml(testRunStarted, envelope); - Assert.IsTrue(html.Contains("window.CUCUMBER_MESSAGES = [{\"testRunStarted\":{\"timestamp\":{\"seconds\":10,\"nanos\":0}}},{\"testRunFinished\":{\"success\":true,\"timestamp\":{\"seconds\":15,\"nanos\":0}}}];")); - } - [TestMethod] public async Task ItWritesTwoMessagesSeparatedByACommaAsync() { Envelope testRunStarted = Envelope.Create(new TestRunStarted(Converters.ToTimestamp(DateTime.UnixEpoch.AddSeconds(10).ToUniversalTime()), null)); Envelope envelope = Envelope.Create(new TestRunFinished(null, true, Converters.ToTimestamp(DateTime.UnixEpoch.AddSeconds(15).ToUniversalTime()), null, null)); string html = await RenderAsHtmlAsync(testRunStarted, envelope); - Assert.IsTrue(html.Contains("window.CUCUMBER_MESSAGES = [{\"testRunStarted\":{\"timestamp\":{\"seconds\":10,\"nanos\":0}}},{\"testRunFinished\":{\"success\":true,\"timestamp\":{\"seconds\":15,\"nanos\":0}}}];")); - } - - [TestMethod] - public void ItEscapesForwardSlashes() - { - Envelope envelope = Envelope.Create(new GherkinDocument( - null, - null, - [new(new Location(0L, 0L), "")] - )); - string html = RenderAsHtml(envelope); - Assert.IsTrue(html.Contains("window.CUCUMBER_MESSAGES = [{\"gherkinDocument\":{\"comments\":[{\"location\":{\"line\":0,\"column\":0},\"text\":\"<\\/script>")] )); string html = await RenderAsHtmlAsync(envelope); - Assert.IsTrue(html.Contains("window.CUCUMBER_MESSAGES = [{\"gherkinDocument\":{\"comments\":[{\"location\":{\"line\":0,\"column\":0},\"text\":\"<\\/script>")] )); string html = await RenderAsHtmlAsync(envelope); - StringAssert.Contains(html, "window.CUCUMBER_MESSAGES = [{\"gherkinDocument\":{\"comments\":[{\"location\":{\"line\":0,\"column\":0},\"text\":\"\\x3C/script>\\x3Cscript>alert('Hello')\\x3C/script>\"}]}}];"}]}}];", - "Expected \"window.CUCUMBER_MESSAGES = [{{\\\"gherkinDocument\\\":{{\\\"comments\\\":[{{\\\"location\\\":{{\\\"line\\\":0,\\\"column\\\":0}},\\\"text\\\":\\\"\\\\x3C/script>\\\\x3Cscript>alert('Hello')\\\\x3C/script>\\\"}}]}}];" + - $"\nbut instead had: \n{html.Substring(html.IndexOf("window.CUCUMBER"))}"); + StringAssert.Contains(html, "window.CUCUMBER_MESSAGES = [{\"gherkinDocument\":{\"comments\":[{\"location\":{\"line\":0,\"column\":0},\"text\":\"\\x3C/script>\\x3Cscript>alert('Hello')\\x3C/script>\"}]}}];", + $"Expected \"window.CUCUMBER_MESSAGES = [{{\\\"gherkinDocument\\\":{{\\\"comments\\\":[{{\\\"location\\\":{{\\\"line\\\":0,\\\"column\\\":0}},\\\"text\\\":\\\"\\\\x3C/script>\\\\x3Cscript>alert('Hello')\\\\x3C/script>\\\"}}]}}];" + + $"\nbut instead had: \n{html.Substring(html.IndexOf("window.CUCUMBER"))}"); } private static string RenderAsHtml(params Envelope[] messages) From 56483557b65ed5704652d3449610277059512201 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 11 Aug 2025 11:46:06 +0200 Subject: [PATCH 15/29] Fix weird doc --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f2ccc92b..c1bc3c15 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ The formatter can be configured with: * A custom page title and icon * Additional CSS to support [styling react components](https://github.com/cucumber/react-components?tab=readme-ov-file#styling). * Additional Javascript for other customisations. - * Replacements for the default Javascript and CSS can be replaced if you are building your own react components. + * The default Javascript and CSS can be replaced to support building custom react components. ## Contributing From 8d5ddf4bbac6b9d6a0431110e34432b86924b088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1sp=C3=A1r=20Nagy?= Date: Mon, 11 Aug 2025 11:26:19 +0200 Subject: [PATCH 16/29] Allow customizing Javascript and CSS resource loader --- .../Cucumber.HtmlFormatter.snk | Bin .../Cucumber.HtmlFormatter.csproj | 2 +- .../HtmlReportSettings.cs | 22 +++++++++++ .../InternalsVisibleTo.cs | 2 + .../MessagesToHtmlWriter.cs | 29 ++++++-------- .../Cucumber.HtmlFormatterTest.csproj | 2 + .../MessagesToHtmlWriterTest.cs | 37 ++++++++++++++++++ 7 files changed, 75 insertions(+), 19 deletions(-) rename dotnet/{Cucumber.HtmlFormatter => }/Cucumber.HtmlFormatter.snk (100%) create mode 100644 dotnet/Cucumber.HtmlFormatter/InternalsVisibleTo.cs diff --git a/dotnet/Cucumber.HtmlFormatter/Cucumber.HtmlFormatter.snk b/dotnet/Cucumber.HtmlFormatter.snk similarity index 100% rename from dotnet/Cucumber.HtmlFormatter/Cucumber.HtmlFormatter.snk rename to dotnet/Cucumber.HtmlFormatter.snk diff --git a/dotnet/Cucumber.HtmlFormatter/Cucumber.HtmlFormatter.csproj b/dotnet/Cucumber.HtmlFormatter/Cucumber.HtmlFormatter.csproj index a82ce1aa..57de49f4 100644 --- a/dotnet/Cucumber.HtmlFormatter/Cucumber.HtmlFormatter.csproj +++ b/dotnet/Cucumber.HtmlFormatter/Cucumber.HtmlFormatter.csproj @@ -9,7 +9,7 @@ 1591 false true - Cucumber.HtmlFormatter.snk + ..\Cucumber.HtmlFormatter.snk diff --git a/dotnet/Cucumber.HtmlFormatter/HtmlReportSettings.cs b/dotnet/Cucumber.HtmlFormatter/HtmlReportSettings.cs index 435672d3..a7611b87 100644 --- a/dotnet/Cucumber.HtmlFormatter/HtmlReportSettings.cs +++ b/dotnet/Cucumber.HtmlFormatter/HtmlReportSettings.cs @@ -36,13 +36,35 @@ public string Icon /// public string CustomScript { get; set; } = string.Empty; + /// + /// Gets or sets the function that loads the main Javascript resource for the HTML report. + /// This should only be customized if you use a custom node.js project to serve the HTML report, + /// for smaller customizations you can use the and property. + /// + public Func JavascriptResourceLoader { get; set; } + + /// + /// Gets or sets the function that loads the main CSS resource for the HTML report. + /// This should only be customized if you use a custom node.js project to serve the HTML report, + /// for smaller customizations you can use the and property. + /// + public Func CssResourceLoader { get; set; } + /// /// Creates a new instance of HtmlReportSettings with default values. /// public HtmlReportSettings() { + JavascriptResourceLoader = LoadJavascriptResource; + CssResourceLoader = LoadCssResource; } private static string LoadDefaultIcon() => MessagesToHtmlWriter.GetResource("icon.url"); + + private static string LoadJavascriptResource() + => MessagesToHtmlWriter.GetResource("main.js"); + + private static string LoadCssResource() + => MessagesToHtmlWriter.GetResource("main.css"); } \ No newline at end of file diff --git a/dotnet/Cucumber.HtmlFormatter/InternalsVisibleTo.cs b/dotnet/Cucumber.HtmlFormatter/InternalsVisibleTo.cs new file mode 100644 index 00000000..8c8b0038 --- /dev/null +++ b/dotnet/Cucumber.HtmlFormatter/InternalsVisibleTo.cs @@ -0,0 +1,2 @@ +using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("Cucumber.HtmlFormatterTest, PublicKey=00240000048000009400000006020000002400005253413100040000010001003d3d7a4a4bb40fd4ad4b18dea92bd57f04946bbce990a7e72a406f026c0af1544510e1069718f7bdc8134fca21b4fb61d8ff139af7c19f2d855a0bf7539667334371478c323ff84e91ccb6a5bc3027fea39ca84658087b7f7f76c30af7adacb315442a0cbec817b71017f363fc4d8a751b98b6e60b00149b08c84ca984ab5ddd")] diff --git a/dotnet/Cucumber.HtmlFormatter/MessagesToHtmlWriter.cs b/dotnet/Cucumber.HtmlFormatter/MessagesToHtmlWriter.cs index b0d52d2f..bb3a8e3f 100644 --- a/dotnet/Cucumber.HtmlFormatter/MessagesToHtmlWriter.cs +++ b/dotnet/Cucumber.HtmlFormatter/MessagesToHtmlWriter.cs @@ -35,7 +35,7 @@ public MessagesToHtmlWriter(StreamWriter writer, Action streamSerializer(w, e); return Task.CompletedTask; }; - _template = GetResource("index.mustache.html"); + _template = LoadTemplateResource(); _jsonInHtmlWriter = new JsonInHtmlWriter(writer); _isAsyncInitialized = false; } @@ -47,7 +47,7 @@ public MessagesToHtmlWriter(StreamWriter writer, Func asyncStreamSerializer(w, e).GetAwaiter().GetResult(); - _template = GetResource("index.mustache.html"); + _template = LoadTemplateResource(); _jsonInHtmlWriter = new JsonInHtmlWriter(writer); _isAsyncInitialized = true; } @@ -59,7 +59,7 @@ private void WritePreMessage() WriteTemplateBetween(_writer, _template, "{{title}}", "{{icon}}"); _writer.Write(_settings.Icon); WriteTemplateBetween(_writer, _template, "{{icon}}", "{{css}}"); - WriteResource(_writer, "main.css"); + _writer.Write(_settings.CssResourceLoader()); WriteTemplateBetween(_writer, _template, "{{css}}", "{{custom_css}}"); _writer.Write(_settings.CustomCss); WriteTemplateBetween(_writer, _template, "{{custom_css}}", "{{messages}}"); @@ -72,7 +72,7 @@ private async Task WritePreMessageAsync() await WriteTemplateBetweenAsync(_writer, _template, "{{title}}", "{{icon}}"); await _writer.WriteAsync(_settings.Icon); await WriteTemplateBetweenAsync(_writer, _template, "{{icon}}", "{{css}}"); - await WriteResourceAsync(_writer, "main.css"); + await _writer.WriteAsync(_settings.CssResourceLoader()); await WriteTemplateBetweenAsync(_writer, _template, "{{css}}", "{{custom_css}}"); await _writer.WriteAsync(_settings.CustomCss); await WriteTemplateBetweenAsync(_writer, _template, "{{custom_css}}", "{{messages}}"); @@ -81,7 +81,7 @@ private async Task WritePreMessageAsync() private void WritePostMessage() { WriteTemplateBetween(_writer, _template, "{{messages}}", "{{script}}"); - WriteResource(_writer, "main.js"); + _writer.Write(_settings.JavascriptResourceLoader()); WriteTemplateBetween(_writer, _template, "{{script}}", "{{custom_script}}"); _writer.Write(_settings.CustomScript); WriteTemplateBetween(_writer, _template, "{{custom_script}}", null); @@ -90,7 +90,7 @@ private void WritePostMessage() private async Task WritePostMessageAsync() { await WriteTemplateBetweenAsync(_writer, _template, "{{messages}}", "{{script}}"); - await WriteResourceAsync(_writer, "main.js"); + await _writer.WriteAsync(_settings.JavascriptResourceLoader()); await WriteTemplateBetweenAsync(_writer, _template, "{{script}}", "{{custom_script}}"); await _writer.WriteAsync(_settings.CustomScript); await WriteTemplateBetweenAsync(_writer, _template, "{{custom_script}}", null); @@ -207,18 +207,6 @@ public async Task DisposeAsync() } } - private void WriteResource(StreamWriter writer, string v) - { - var resource = GetResource(v); - writer.Write(resource); - } - - private async Task WriteResourceAsync(StreamWriter writer, string v) - { - var resource = GetResource(v); - await writer.WriteAsync(resource); - } - private void WriteTemplateBetween(StreamWriter writer, string template, string? begin, string? end) { CalculateBeginAndLength(template, begin, end, out var beginIndex, out var lengthToWrite); @@ -247,4 +235,9 @@ internal static string GetResource(string name) var resource = new StreamReader(resourceStream).ReadToEnd(); return resource; } + + private static string LoadTemplateResource() + { + return GetResource("index.mustache.html"); + } } diff --git a/dotnet/Cucumber.HtmlFormatterTest/Cucumber.HtmlFormatterTest.csproj b/dotnet/Cucumber.HtmlFormatterTest/Cucumber.HtmlFormatterTest.csproj index 7036fd90..a073065e 100644 --- a/dotnet/Cucumber.HtmlFormatterTest/Cucumber.HtmlFormatterTest.csproj +++ b/dotnet/Cucumber.HtmlFormatterTest/Cucumber.HtmlFormatterTest.csproj @@ -5,6 +5,8 @@ latest enable enable + true + ..\Cucumber.HtmlFormatter.snk diff --git a/dotnet/Cucumber.HtmlFormatterTest/MessagesToHtmlWriterTest.cs b/dotnet/Cucumber.HtmlFormatterTest/MessagesToHtmlWriterTest.cs index df4a470c..d979f45b 100644 --- a/dotnet/Cucumber.HtmlFormatterTest/MessagesToHtmlWriterTest.cs +++ b/dotnet/Cucumber.HtmlFormatterTest/MessagesToHtmlWriterTest.cs @@ -91,6 +91,43 @@ public async Task ItWritesCustomScript() StringAssert.Contains(html, "console.log(\"Hello world\");"); } + [TestMethod] + public async Task ItWritesDefaultJavascriptResource() + { + var expectedJavascriptResource = MessagesToHtmlWriter.GetResource("main.js"); + + string html = await RenderAsHtmlAsync(); + StringAssert.Contains(html, expectedJavascriptResource); + } + + [TestMethod] + public async Task ItWritesCustomJavascriptResource() + { + string CustomJavascriptResourceLoader() => "console.log(\"Hello world\");"; + + string html = await RenderAsHtmlAsync(new HtmlReportSettings { JavascriptResourceLoader = CustomJavascriptResourceLoader }); + StringAssert.Contains(html, "console.log(\"Hello world\");"); + } + + [TestMethod] + public async Task ItWritesDefaultCssResource() + { + var expectedCssResource = MessagesToHtmlWriter.GetResource("main.css"); + + string html = await RenderAsHtmlAsync(); + StringAssert.Contains(html, expectedCssResource); + } + + [TestMethod] + public async Task ItWritesCustomCssResource() + { + string CustomCssResourceLoader() => "p { color: red; }"; + + string html = await RenderAsHtmlAsync(new HtmlReportSettings { CssResourceLoader = CustomCssResourceLoader }); + StringAssert.Contains(html, "p { color: red; }"); + } + + [TestMethod] public async Task ItWritesNoMessageToHtmlAsync() { From d8071d472b792ee041ebd7c1bc4687a490683849 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 11 Aug 2025 11:58:25 +0200 Subject: [PATCH 17/29] Update more docs --- .../htmlformatter/MessagesToHtmlWriter.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java b/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java index dca7c9f3..dcdf0460 100644 --- a/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java +++ b/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java @@ -269,7 +269,7 @@ public Builder title(String title) { *

* The {@code icon} is any valid {@code href} value. * - * @param icon the custom icon. + * @param icon a supplier for the custom icon. * @return this builder */ public Builder icon(Supplier icon) { @@ -293,9 +293,11 @@ public Builder icon(String icon) { /** * Sets default css for the report. *

- * The default script styles the cucumber report. + * The default script styles the cucumber report. Unless you are + * building your own html report you should use + * {@link #customCss(Supplier)} instead. * - * @param css the custom css. + * @param css a supplier for the default css. * @return this builder */ public Builder css(Supplier css) { @@ -308,8 +310,9 @@ public Builder css(Supplier css) { *

* The custom css is applied after the default css. * - * @param customCss the custom css. + * @param customCss a supplier for the custom css. * @return this builder + * @see Cucumber - React Components - Styling */ public Builder customCss(Supplier customCss) { this.customCss = requireNonNull(customCss); @@ -320,8 +323,10 @@ public Builder customCss(Supplier customCss) { * Replaces default script for the report. *

* The default script renders the cucumber messages into a report. + * Unless you are building your own html report you should use + * {@link #customScript(Supplier)} instead. * - * @param script the custom script. + * @param script a supplier for the default script. * @return this builder */ public Builder script(Supplier script) { @@ -334,7 +339,7 @@ public Builder script(Supplier script) { *

* The custom script is applied after the default script. * - * @param customScript the custom script. + * @param customScript a supplier for the custom script. * @return this builder */ public Builder customScript(Supplier customScript) { From 3e35e662827dc38b98cdb50383b283312f7dbe31 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 11 Aug 2025 12:08:12 +0200 Subject: [PATCH 18/29] Simplify make file --- Makefile | 9 --------- javascript/package.json | 2 +- javascript/test/acceptance.spec.ts | 2 +- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index a2edb234..4e79852a 100644 --- a/Makefile +++ b/Makefile @@ -18,27 +18,18 @@ clean: ## Remove javascript built module and related artifacts from java and rub ruby/assets/index.mustache.html: javascript/src/index.mustache.html cp $< $@ -ruby/assets/icon.url: javascript/src/icon.url - cp $< $@ - ruby/assets/%: javascript/dist/% cp $< $@ java/src/main/resources/io/cucumber/htmlformatter/index.mustache.html: javascript/src/index.mustache.html cp $< $@ -java/src/main/resources/io/cucumber/htmlformatter/icon.url: javascript/src/icon.url - cp $< $@ - java/src/main/resources/io/cucumber/htmlformatter/%: javascript/dist/% cp $< $@ dotnet/Cucumber.HtmlFormatter/Resources/index.mustache.html: javascript/src/index.mustache.html cp $< $@ -dotnet/Cucumber.HtmlFormatter/Resources/icon.url: javascript/src/icon.url - cp $< $@ - dotnet/Cucumber.HtmlFormatter/Resources/%: javascript/dist/% cp $< $@ diff --git a/javascript/package.json b/javascript/package.json index af0a5f30..d94366e6 100644 --- a/javascript/package.json +++ b/javascript/package.json @@ -15,7 +15,7 @@ "build:tsc": "tsc --build tsconfig.build.json", "build:webpack": "webpack", "build": "npm run clean && npm run build:tsc && npm run prepare && npm run build:webpack", - "prepare": "shx mkdir -p dist/src && shx cp src/*.scss dist/src && shx cp src/index.mustache.html dist/src && shx cp src/icon.url dist/src", + "prepare": "shx mkdir -p dist/src && shx cp src/*.scss dist/src && shx cp src/index.mustache.html dist/src && shx cp src/icon.url dist/", "test": "mocha", "prepublishOnly": "npm run build", "fix": "eslint --max-warnings 0 --fix src test && prettier --write src test", diff --git a/javascript/test/acceptance.spec.ts b/javascript/test/acceptance.spec.ts index a5598e8a..d1810a42 100644 --- a/javascript/test/acceptance.spec.ts +++ b/javascript/test/acceptance.spec.ts @@ -25,7 +25,7 @@ test.beforeAll(async () => { new CucumberHtmlStream( path.join(__dirname, '../dist/main.css'), path.join(__dirname, '../dist/main.js'), - path.join(__dirname, '../dist/src/icon.url') + path.join(__dirname, '../dist/icon.url') ), fs.createWriteStream(outputFile) ) From 0253d9bb41187bf947a6d55f0e9636f6c7d98367 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 11 Aug 2025 12:11:51 +0200 Subject: [PATCH 19/29] Reduce diff on MessagesToHtmlWriter --- .../htmlformatter/MessagesToHtmlWriter.java | 67 +++++++++---------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java b/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java index dcdf0460..17f1608e 100644 --- a/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java +++ b/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java @@ -75,6 +75,12 @@ private MessagesToHtmlWriter( this.script = script; } + private static OutputStreamWriter createWriter(OutputStream outputStream) { + return new OutputStreamWriter( + requireNonNull(outputStream), + StandardCharsets.UTF_8); + } + private static String readTemplate() { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -87,11 +93,18 @@ private static String readTemplate() { throw new RuntimeException("Could not read resource index.mustache.html", e); } } + private static InputStream createInputStream(String text) { + return new ByteArrayInputStream(text.getBytes(UTF_8)); + } - private static OutputStreamWriter createWriter(OutputStream outputStream) { - return new OutputStreamWriter( - requireNonNull(outputStream), - StandardCharsets.UTF_8); + private static InputStream createEmptyInputStream() { + return new ByteArrayInputStream(new byte[0]); + } + + private static InputStream getResource(String name) { + InputStream resource = MessagesToHtmlWriter.class.getResourceAsStream(name); + requireNonNull(resource, name + " could not be loaded"); + return resource; } /** @@ -104,12 +117,24 @@ public static Builder builder(Serializer serializer) { return new Builder(serializer); } - private static InputStream createInputStream(String text) { - return new ByteArrayInputStream(text.getBytes(UTF_8)); + 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 static InputStream createEmptyInputStream() { - return new ByteArrayInputStream(new byte[0]); + 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, String begin, String end) @@ -131,32 +156,6 @@ private static void writeResource(Writer writer, InputStream resource) throws IO } } - private static InputStream getResource(String name) { - InputStream resource = MessagesToHtmlWriter.class.getResourceAsStream(name); - requireNonNull(resource, name + " could not be loaded"); - 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); - } - /** * Writes a cucumber message to the html output. * From 890f024eba8d7f179b17f2773d85b6f28ff249a8 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 11 Aug 2025 12:13:36 +0200 Subject: [PATCH 20/29] Reduce diff on MessagesToHtmlWriter --- .../htmlformatter/MessagesToHtmlWriter.java | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java b/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java index 17f1608e..61e1e4e2 100644 --- a/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java +++ b/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java @@ -137,25 +137,6 @@ private void writePostMessage() throws IOException { writeTemplateBetween(writer, template, "{{custom_script}}", null); } - private static void writeTemplateBetween(Writer writer, String template, String begin, String end) - throws IOException { - int beginIndex = begin == null ? 0 : template.indexOf(begin) + begin.length(); - int endIndex = end == null ? template.length() : template.indexOf(end); - writer.write(template.substring(beginIndex, endIndex)); - } - - private static void writeResource(Writer writer, Supplier resource) throws IOException { - writeResource(writer, resource.get()); - } - - private static void writeResource(Writer writer, InputStream resource) throws IOException { - BufferedReader reader = new BufferedReader(new InputStreamReader(resource, UTF_8)); - char[] buffer = new char[1024]; - for (int read = reader.read(buffer); read != -1; read = reader.read(buffer)) { - writer.write(buffer, 0, read); - } - } - /** * Writes a cucumber message to the html output. * @@ -211,6 +192,26 @@ public void close() throws IOException { } } + + private static void writeTemplateBetween(Writer writer, String template, String begin, String end) + throws IOException { + int beginIndex = begin == null ? 0 : template.indexOf(begin) + begin.length(); + int endIndex = end == null ? template.length() : template.indexOf(end); + writer.write(template.substring(beginIndex, endIndex)); + } + + private static void writeResource(Writer writer, Supplier resource) throws IOException { + writeResource(writer, resource.get()); + } + + private static void writeResource(Writer writer, InputStream resource) throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(resource, UTF_8)); + char[] buffer = new char[1024]; + for (int read = reader.read(buffer); read != -1; read = reader.read(buffer)) { + writer.write(buffer, 0, read); + } + } + /** * Serializes a message to JSON. */ From 88a665b92072b1569bcd683cc61150e9f4de3f25 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 11 Aug 2025 12:15:23 +0200 Subject: [PATCH 21/29] Reduce diff on MessagesToHtmlWriter --- .../htmlformatter/MessagesToHtmlWriter.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java b/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java index 61e1e4e2..8443db90 100644 --- a/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java +++ b/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java @@ -107,16 +107,6 @@ private static InputStream getResource(String name) { return resource; } - /** - * 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); - } - private void writePreMessage() throws IOException { writeTemplateBetween(writer, template, null, "{{title}}"); writeResource(writer, title); @@ -192,6 +182,15 @@ public void close() throws IOException { } } + /** + * 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); + } private static void writeTemplateBetween(Writer writer, String template, String begin, String end) throws IOException { From 3c6d7a0c8f268d583e73fe2f985d6148ba5f0597 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 11 Aug 2025 12:17:06 +0200 Subject: [PATCH 22/29] Reduce diff on MessagesToHtmlWriter --- .../htmlformatter/MessagesToHtmlWriter.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java b/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java index 8443db90..c410b682 100644 --- a/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java +++ b/java/src/main/java/io/cucumber/htmlformatter/MessagesToHtmlWriter.java @@ -182,16 +182,6 @@ public void close() throws IOException { } } - /** - * 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); - } - private static void writeTemplateBetween(Writer writer, String template, String begin, String end) throws IOException { int beginIndex = begin == null ? 0 : template.indexOf(begin) + begin.length(); @@ -238,6 +228,16 @@ 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"); From aee2b0a17a45ba59244600a42434612934d2a9ea Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 11 Aug 2025 12:23:33 +0200 Subject: [PATCH 23/29] Fix make in CI? --- javascript/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/package.json b/javascript/package.json index d94366e6..412e85f9 100644 --- a/javascript/package.json +++ b/javascript/package.json @@ -15,7 +15,7 @@ "build:tsc": "tsc --build tsconfig.build.json", "build:webpack": "webpack", "build": "npm run clean && npm run build:tsc && npm run prepare && npm run build:webpack", - "prepare": "shx mkdir -p dist/src && shx cp src/*.scss dist/src && shx cp src/index.mustache.html dist/src && shx cp src/icon.url dist/", + "prepare": "shx mkdir -p dist/src && shx cp src/*.scss dist/src && shx cp src/index.mustache.html dist/src && shx cp src/icon.url dist", "test": "mocha", "prepublishOnly": "npm run build", "fix": "eslint --max-warnings 0 --fix src test && prettier --write src test", From e5c23123095d7b2cfd59c04c733d87fd91019f6e Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 11 Aug 2025 12:34:07 +0200 Subject: [PATCH 24/29] Simplify make, differently --- Makefile | 6 +++--- javascript/package.json | 2 +- javascript/src/CucumberHtmlStream.spec.ts | 3 +-- javascript/src/CucumberHtmlStream.ts | 2 +- javascript/test/acceptance.spec.ts | 2 +- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 4e79852a..3ad724e0 100644 --- a/Makefile +++ b/Makefile @@ -15,19 +15,19 @@ clean: ## Remove javascript built module and related artifacts from java and rub rm -rf $(ruby_assets) $(java_assets) $(dotnet_assets) javascript/dist .PHONY: .clean -ruby/assets/index.mustache.html: javascript/src/index.mustache.html +ruby/assets/%: javascript/dist/src/% cp $< $@ ruby/assets/%: javascript/dist/% cp $< $@ -java/src/main/resources/io/cucumber/htmlformatter/index.mustache.html: javascript/src/index.mustache.html +java/src/main/resources/io/cucumber/htmlformatter/%: javascript/dist/src/% cp $< $@ java/src/main/resources/io/cucumber/htmlformatter/%: javascript/dist/% cp $< $@ -dotnet/Cucumber.HtmlFormatter/Resources/index.mustache.html: javascript/src/index.mustache.html +dotnet/Cucumber.HtmlFormatter/Resources/%: javascript/dist/src/% cp $< $@ dotnet/Cucumber.HtmlFormatter/Resources/%: javascript/dist/% diff --git a/javascript/package.json b/javascript/package.json index 412e85f9..89b45c23 100644 --- a/javascript/package.json +++ b/javascript/package.json @@ -15,7 +15,7 @@ "build:tsc": "tsc --build tsconfig.build.json", "build:webpack": "webpack", "build": "npm run clean && npm run build:tsc && npm run prepare && npm run build:webpack", - "prepare": "shx mkdir -p dist/src && shx cp src/*.scss dist/src && shx cp src/index.mustache.html dist/src && shx cp src/icon.url dist", + "prepare": "shx mkdir -p dist/src && shx cp src/*.scss dist/src && shx cp src/index.mustache.html dist/src && shx cp src/icon.url dist/src", "test": "mocha", "prepublishOnly": "npm run build", "fix": "eslint --max-warnings 0 --fix src test && prettier --write src test", diff --git a/javascript/src/CucumberHtmlStream.spec.ts b/javascript/src/CucumberHtmlStream.spec.ts index a31b1273..ce6fafbd 100644 --- a/javascript/src/CucumberHtmlStream.spec.ts +++ b/javascript/src/CucumberHtmlStream.spec.ts @@ -22,8 +22,7 @@ async function renderAsHtml( sink.on('finish', () => resolve(html)) const cucumberHtmlStream = new CucumberHtmlStream( `${__dirname}/dummy.css`, - `${__dirname}/dummy.js`, - `${__dirname}/icon.url` + `${__dirname}/dummy.js` ) cucumberHtmlStream.on('error', reject) cucumberHtmlStream.pipe(sink) diff --git a/javascript/src/CucumberHtmlStream.ts b/javascript/src/CucumberHtmlStream.ts index eca5087f..8738fb4d 100644 --- a/javascript/src/CucumberHtmlStream.ts +++ b/javascript/src/CucumberHtmlStream.ts @@ -17,7 +17,7 @@ export class CucumberHtmlStream extends Transform { constructor( private readonly cssPath: string = path.join(__dirname, '..', 'main.css'), private readonly jsPath: string = path.join(__dirname, '..', 'main.js'), - private readonly iconPath: string = path.join(__dirname, '..', 'icon.url') + private readonly iconPath: string = path.join(__dirname, 'icon.url') ) { super({ objectMode: true }) } diff --git a/javascript/test/acceptance.spec.ts b/javascript/test/acceptance.spec.ts index d1810a42..a5598e8a 100644 --- a/javascript/test/acceptance.spec.ts +++ b/javascript/test/acceptance.spec.ts @@ -25,7 +25,7 @@ test.beforeAll(async () => { new CucumberHtmlStream( path.join(__dirname, '../dist/main.css'), path.join(__dirname, '../dist/main.js'), - path.join(__dirname, '../dist/icon.url') + path.join(__dirname, '../dist/src/icon.url') ), fs.createWriteStream(outputFile) ) From 5b31bb63136d430fc28a8fc4f69dd4d87982a05a Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 11 Aug 2025 12:39:00 +0200 Subject: [PATCH 25/29] Debug make --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 3ad724e0..3d911208 100644 --- a/Makefile +++ b/Makefile @@ -39,3 +39,5 @@ javascript/dist/main.css: javascript/dist/main.js javascript/dist/main.js: javascript/package.json $(javascript_source) cd javascript && npm install-ci-test && npm run build + cd .. + tree . From 95215ef2beb3fa06f7159e1244ff2b8af3d91815 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 11 Aug 2025 12:40:28 +0200 Subject: [PATCH 26/29] Debug make --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3d911208..af442987 100644 --- a/Makefile +++ b/Makefile @@ -40,4 +40,5 @@ javascript/dist/main.css: javascript/dist/main.js javascript/dist/main.js: javascript/package.json $(javascript_source) cd javascript && npm install-ci-test && npm run build cd .. - tree . + ls javascript/dist + ls javascript/dist/src From ed6ae72c937a08cebbbc5ec6cb128092bd112cf5 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 11 Aug 2025 12:45:09 +0200 Subject: [PATCH 27/29] Simplify make, differently --- Makefile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index af442987..71a69c56 100644 --- a/Makefile +++ b/Makefile @@ -33,12 +33,13 @@ dotnet/Cucumber.HtmlFormatter/Resources/%: javascript/dist/src/% dotnet/Cucumber.HtmlFormatter/Resources/%: javascript/dist/% cp $< $@ +javascript/dist/index.mustache.html: javascript/dist/main.js + +javascript/dist/icon.url: javascript/dist/main.js + javascript/dist/main.js.LICENSE.txt: javascript/dist/main.js javascript/dist/main.css: javascript/dist/main.js javascript/dist/main.js: javascript/package.json $(javascript_source) cd javascript && npm install-ci-test && npm run build - cd .. - ls javascript/dist - ls javascript/dist/src From ea3bda4326667159628ec562d6bc79efbc0862c1 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 11 Aug 2025 12:47:22 +0200 Subject: [PATCH 28/29] Simplify make, differently --- Makefile | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 71a69c56..7e4dfabf 100644 --- a/Makefile +++ b/Makefile @@ -33,13 +33,9 @@ dotnet/Cucumber.HtmlFormatter/Resources/%: javascript/dist/src/% dotnet/Cucumber.HtmlFormatter/Resources/%: javascript/dist/% cp $< $@ -javascript/dist/index.mustache.html: javascript/dist/main.js +javascript/dist/src/%: javascript/dist/main.js -javascript/dist/icon.url: javascript/dist/main.js - -javascript/dist/main.js.LICENSE.txt: javascript/dist/main.js - -javascript/dist/main.css: javascript/dist/main.js +javascript/dist/%: javascript/dist/main.js javascript/dist/main.js: javascript/package.json $(javascript_source) cd javascript && npm install-ci-test && npm run build From 9b958647604d6c03eb7b452aba7081be73455e4a Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Mon, 11 Aug 2025 12:48:55 +0200 Subject: [PATCH 29/29] Simplify make, differently --- Makefile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 7e4dfabf..0c38ccbe 100644 --- a/Makefile +++ b/Makefile @@ -33,9 +33,13 @@ dotnet/Cucumber.HtmlFormatter/Resources/%: javascript/dist/src/% dotnet/Cucumber.HtmlFormatter/Resources/%: javascript/dist/% cp $< $@ -javascript/dist/src/%: javascript/dist/main.js +javascript/dist/src/index.mustache.html: javascript/dist/main.js -javascript/dist/%: javascript/dist/main.js +javascript/dist/src/icon.url: javascript/dist/main.js + +javascript/dist/main.js.LICENSE.txt: javascript/dist/main.js + +javascript/dist/main.css: javascript/dist/main.js javascript/dist/main.js: javascript/package.json $(javascript_source) cd javascript && npm install-ci-test && npm run build