Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ The optional `Reference` field links a spec to an external ticket or resource. I
- In generated Java: rendered as a `@see <a href="url">text</a>` Javadoc tag on the abstract class.
- In generated docs: rendered as an AsciiDoc hyperlink (`link:url[text]`) below the feature title.

The optional `Changelog` field allows you to document changes, background information, or technical rationale for the specification. It accepts arbitrary free text:
- In generated Java: rendered as a `@Changelog` tag in the Javadoc of the abstract class.
- In generated docs: inserted after the introduction (version/reference), only when present.

## Code generation

Bjoern will then generate the TestClasses based on the spec
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ private void addJavaDoc(Bjoern bjoern, Builder featureClassBuilder) {
if (StringUtils.isNotBlank(bjoern.getReference())) {
javadoc.append("\n@see ").append(bjoern.getReferenceAsJavadoc());
}
if (StringUtils.isNotBlank(bjoern.getChangelog())) {
javadoc.append("\n@Changelog ").append(bjoern.getChangelog());
}
featureClassBuilder.addJavadoc("$L", javadoc.toString());
}

Comment thread
Mehtrick marked this conversation as resolved.
Expand Down
23 changes: 22 additions & 1 deletion src/main/java/de/mehtrick/bjoern/parser/modell/Bjoern.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package de.mehtrick.bjoern.parser.modell;

import de.mehtrick.bjoern.parser.BjoernTextParser;
import de.mehtrick.bjoern.parser.replacer.AsciidocReplacer;
import org.apache.commons.text.StringEscapeUtils;

import java.util.List;
Expand All @@ -15,6 +16,7 @@ public class Bjoern {
private String feature;
private String version;
private String reference;
private String changelog;
private BjoernBackground background;
private List<BjoernScenario> scenarios;
private String filePath;
Expand All @@ -23,6 +25,7 @@ public Bjoern(BjoernZGRModell yamlModell, String path) {
setFeature(yamlModell.getFeature());
setVersion(yamlModell.getVersion());
setReference(yamlModell.getReference());
setChangelog(yamlModell.getChangelog());
setScenarios(yamlModell.getScenarios().stream().map(BjoernScenario::new).collect(Collectors.toList()));
setFilePath(path);
if (yamlModell.getBackground() != null) {
Expand Down Expand Up @@ -58,6 +61,24 @@ public void setReference(String reference) {
this.reference = reference;
}

public String getChangelog() {
return this.changelog;
}

public void setChangelog(String changelog) {
this.changelog = changelog;
}

/**
* Returns the changelog with AsciiDoc-specific characters escaped (e.g. pipe characters).
*/
public String getChangelogAsAsciidoc() {
if (changelog == null) {
return null;
}
return AsciidocReplacer.replace(changelog);
}

/**
* Returns the reference formatted as a Javadoc hyperlink if it is a Markdown link ([text](url))
* with an allowed URL scheme (http/https), otherwise returns the plain reference text.
Expand Down Expand Up @@ -136,6 +157,6 @@ public void setFilePath(String filePath) {


public String toString() {
return "Bjoern(feature=" + this.getFeature() + ", version=" + this.getVersion() + ", reference=" + this.getReference() + ", background=" + this.getBackground() + ", scenarios=" + this.getScenarios() + ", filePath=" + this.getFilePath() + ")";
return "Bjoern(feature=" + this.getFeature() + ", version=" + this.getVersion() + ", reference=" + this.getReference() + ", changelog=" + this.getChangelog() + ", background=" + this.getBackground() + ", scenarios=" + this.getScenarios() + ", filePath=" + this.getFilePath() + ")";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({ "Feature", "Version", "Reference", "Scenarios" })
@JsonPropertyOrder({ "Feature", "Version", "Reference", "Changelog", "Scenarios" })
public class BjoernZGRModell implements Serializable {

@JsonProperty("Feature")
Expand All @@ -27,6 +27,9 @@ public class BjoernZGRModell implements Serializable {
@JsonProperty("Reference")
private String reference;

@JsonProperty("Changelog")
private String changelog;

@JsonProperty("Background")
private BjoernZGRBackground background;

Expand Down Expand Up @@ -64,6 +67,16 @@ public void setReference(String reference) {
this.reference = reference;
}

@JsonProperty("Changelog")
public String getChangelog() {
return changelog;
}

@JsonProperty("Changelog")
public void setChangelog(String changelog) {
this.changelog = changelog;
}

@JsonProperty("Scenarios")
public List<BjoernZGRScenario> getScenarios() {
return bjoernZGRScenarios;
Expand All @@ -85,6 +98,6 @@ public void setBackground(BjoernZGRBackground background) {
}

public String toString() {
return "BjoernZGRModell(feature=" + this.getFeature() + ", version=" + this.getVersion() + ", reference=" + this.getReference() + ", background=" + this.getBackground() + ", bjoernZGRScenarios=" + this.bjoernZGRScenarios + ")";
return "BjoernZGRModell(feature=" + this.getFeature() + ", version=" + this.getVersion() + ", reference=" + this.getReference() + ", changelog=" + this.getChangelog() + ", background=" + this.getBackground() + ", bjoernZGRScenarios=" + this.bjoernZGRScenarios + ")";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

public enum BjoernKeywords {

GIVEN("Given:"), WHEN("When:"), THEN("Then:"), BACKGROUND("Background:"), FEATURE("Feature:"), VERSION("Version:"), REFERENCE("Reference:"), SCENARIO("- Scenario:"), SCENARIOS("Scenarios:"), STATEMENT("-");
GIVEN("Given:"), WHEN("When:"), THEN("Then:"), BACKGROUND("Background:"), FEATURE("Feature:"), VERSION("Version:"), REFERENCE("Reference:"), CHANGELOG("Changelog:"), SCENARIO("- Scenario:"), SCENARIOS("Scenarios:"), STATEMENT("-");
Comment thread
Mehtrick marked this conversation as resolved.

public String keyword;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,39 @@ public InvalidKeywordValidation(String errorText) {

@Override
public void validate(String[] lines, int index) throws BjoernValidationsException {
if (isInsideChangelogBlock(lines, index)) {
return;
}
String trimmedLine = getTrimmedLine(lines, index);
boolean lineDoesNotStartWithKnownKeyword = BjoernKeywords.getKeywordValues().stream().noneMatch(trimmedLine::startsWith);
if (lineDoesNotStartWithKnownKeyword) {
throw new BjoernValidationsException(index, errorText, lines[index], BjoernKeywords.getKeywordsAsSingleString());
}
}

/**
* Returns true when the current line is an indented continuation of a YAML block scalar Changelog value
* (i.e. the Changelog key was followed by a block scalar indicator such as | or >).
*/
private boolean isInsideChangelogBlock(String[] lines, int index) {
if (lines[index].isEmpty() || !Character.isWhitespace(lines[index].charAt(0))) {
return false;
}
for (int i = index - 1; i >= 0; i--) {
if (lines[i].trim().isEmpty()) {
continue;
}
if (!Character.isWhitespace(lines[i].charAt(0))) {
// Found the last root-level non-empty line
int colonIndex = lines[i].indexOf(':');
if (colonIndex < 0) {
return false;
}
String afterKey = lines[i].substring(colonIndex + 1).trim();
return lines[i].trim().startsWith(BjoernKeywords.CHANGELOG.keyword)
&& (afterKey.startsWith("|") || afterKey.startsWith(">"));
}
}
return false;
}
}
4 changes: 4 additions & 0 deletions src/main/resources/asciidoc.ftlh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ Version: ${version}
<#if referenceAsAsciidoc?? && referenceAsAsciidoc?has_content>
Reference: ${referenceAsAsciidoc}

</#if>
<#if changelogAsAsciidoc?? && changelogAsAsciidoc?has_content>
Changelog: ${changelogAsAsciidoc}

</#if>

<#if gitHistory?? && gitHistory?has_content>
Expand Down
23 changes: 23 additions & 0 deletions src/test/java/de/mehtrick/bjoern/asciidoc/AsciiDocBuildTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,29 @@ public void testDocGenerationWithReference() throws IOException, BjoernMissingPr
assertThat(content).contains("Reference: link:https://example.com/TICKET-123[TICKET-123]");
}

@Test
@DisplayName("Test Doc Generation with Changelog")
public void testDocGenerationWithChangelog() throws IOException, BjoernMissingPropertyException, NotSupportedJunitVersionException {
BjoernDocApplication.main(new String[]{"path=src/test/resources/changelog.zgr", "docdir=src/gen/resources"});
File generatedFile = new File("src/gen/resources/changelog.adoc");
assertThat(generatedFile).exists();
String content = new String(Files.readAllBytes(generatedFile.toPath()), StandardCharsets.UTF_8);
assertThat(content).contains("= Test mit Changelog");
assertThat(content).contains("First line of the changelog.");
assertThat(content).contains("Second line with a pipe \\| character.");
}

@Test
@DisplayName("Test Doc Generation without Changelog does not include changelog section")
public void testDocGenerationWithoutChangelog() throws IOException, BjoernMissingPropertyException, NotSupportedJunitVersionException {
BjoernDocApplication.main(new String[]{"path=src/test/resources/version.zgr", "docdir=src/gen/resources"});
File generatedFile = new File("src/gen/resources/version.adoc");
assertThat(generatedFile).exists();
String content = new String(Files.readAllBytes(generatedFile.toPath()), StandardCharsets.UTF_8);
assertThat(content).contains("= Test mit Version");
assertThat(content).doesNotContain("Changelog:");
}

@Test
@DisplayName("Test Generation of Empty Given")
public void testGenerationOfEmptyGiven() throws BjoernMissingPropertyException, FileNotFoundException, NotSupportedJunitVersionException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,17 @@ void testJavadocWithVersion() {
Assertions.assertThat(mappedFeature.javadoc.toString()).contains("1.0.0");
}

@Test
void testJavadocWithChangelog() {
//given
bjoern = getBjoern("src/test/resources/changelog.zgr");
//when
TypeSpec mappedFeature = new BjoernFeatureTestClassBuilder(bjoernCodeGeneratorConfig).build(bjoern).getFeatureClass();
//then
Assertions.assertThat(mappedFeature.javadoc.toString()).contains("Test mit Changelog");
Assertions.assertThat(mappedFeature.javadoc.toString()).contains("@Changelog");
Assertions.assertThat(mappedFeature.javadoc.toString()).contains("First line of the changelog.");
Assertions.assertThat(mappedFeature.javadoc.toString()).contains("Second line with a pipe | character.");
}

}
40 changes: 38 additions & 2 deletions src/test/java/de/mehtrick/bjoern/parser/BjoernValidatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ public void testInvalidKeyword() {
Assertions.assertThatExceptionOfType(BjoernValidatorException.class).isThrownBy(() -> {
bjoernValidator.validate(" WrongKeyword \r\n \r\n anotherWrongOne", "defaultpath");
}
).withMessageContaining("ValidationError at line 1: The line starts with an invalid Keyword. Found \" WrongKeyword \". Allowed Keywords are: Given:,When:,Then:,Background:,Feature:,Version:,Reference:,- Scenario:,Scenarios:,-. This check is case-sensitive!")
.withMessageContaining("ValidationError at line 3: The line starts with an invalid Keyword. Found \" anotherWrongOne\". Allowed Keywords are: Given:,When:,Then:,Background:,Feature:,Version:,Reference:,- Scenario:,Scenarios:,-. This check is case-sensitive!");
).withMessageContaining("ValidationError at line 1: The line starts with an invalid Keyword. Found \" WrongKeyword \". Allowed Keywords are: Given:,When:,Then:,Background:,Feature:,Version:,Reference:,Changelog:,- Scenario:,Scenarios:,-. This check is case-sensitive!")
.withMessageContaining("ValidationError at line 3: The line starts with an invalid Keyword. Found \" anotherWrongOne\". Allowed Keywords are: Given:,When:,Then:,Background:,Feature:,Version:,Reference:,Changelog:,- Scenario:,Scenarios:,-. This check is case-sensitive!");
Comment thread
Mehtrick marked this conversation as resolved.
}

@Test
Expand Down Expand Up @@ -159,4 +159,40 @@ public void correctsmaple() {
//THEN
//No Error
}

@Test
public void changelogMultilineLiteralBlock() {
String zgr = "Feature: Feature\r\n" +
"Changelog: |\r\n" +
" First line of changelog.\r\n" +
" Second line with | pipe.\r\n" +
"Scenarios: \r\n" +
" - Scenario: Scenario \r\n" +
" Given: \r\n" +
" - Ein Benutzer";

//WHEN
bjoernValidator.validate(zgr, "default");

//THEN
//No Error - multiline Changelog block scalar continuation lines must not trigger InvalidKeywordValidation
}

@Test
public void changelogMultilineFoldedBlock() {
String zgr = "Feature: Feature\r\n" +
"Changelog: >\r\n" +
" First line of changelog.\r\n" +
" Second line.\r\n" +
"Scenarios: \r\n" +
" - Scenario: Scenario \r\n" +
" Given: \r\n" +
" - Ein Benutzer";

//WHEN
bjoernValidator.validate(zgr, "default");

//THEN
//No Error - folded block scalar continuation lines must also be accepted
}
}
12 changes: 12 additions & 0 deletions src/test/resources/changelog.zgr
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Feature: Test mit Changelog
Changelog: |
First line of the changelog.
Second line with a pipe | character.
Scenarios:
- Scenario: Einfaches Szenario
Given:
- Ein Benutzer
When:
- Benutzer tut etwas
Then:
- Ergebnis ist korrekt
Loading