diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/StepAccess.java b/jgiven-core/src/main/java/com/tngtech/jgiven/CurrentStep.java similarity index 67% rename from jgiven-core/src/main/java/com/tngtech/jgiven/StepAccess.java rename to jgiven-core/src/main/java/com/tngtech/jgiven/CurrentStep.java index e20f4a12b7..b3bf09d4a2 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/StepAccess.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/CurrentStep.java @@ -1,13 +1,12 @@ package com.tngtech.jgiven; - import com.tngtech.jgiven.attachment.Attachment; /** - * This interface can be injected into a stage by using the {@link com.tngtech.jgiven.annotation.ExpectedScenarioState} + * This interface can be injected into a stage by using the {@link com.tngtech.jgiven.annotation.ScenarioState} * annotation. It provides programmatic access to the current executed step. */ -public interface StepAccess { +public interface CurrentStep { /** * Adds an attachment to the current step @@ -16,14 +15,6 @@ public interface StepAccess { */ void addAttachment( Attachment attachment ); - /** - * Set the description of the current step - * - * @param description the description to set - * @see com.tngtech.jgiven.annotation.Description - */ - void setDescription( String description ); - /** * Set the extended description of the current step * diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/Attachment.java b/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/Attachment.java index 317407ae34..39c1dd656a 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/Attachment.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/Attachment.java @@ -1,10 +1,182 @@ package com.tngtech.jgiven.attachment; +import java.io.*; +import java.nio.charset.Charset; + +import javax.xml.bind.DatatypeConverter; + +import com.google.common.io.ByteStreams; +import com.google.common.io.CharStreams; +import com.google.common.io.Files; +import com.tngtech.jgiven.impl.util.ResourceUtil; + /** - * Represents an attachment of a step + * Represents an attachment of a step. + * Attachments must be representable as a String so that it can be stored as JSON. + * For binary attachments this means that they have to be encoded with Base64. + * In addition, attachments must have a media type so that reporters know + * how to present an attachment. + * */ -public interface Attachment { - String asString(); +public class Attachment { + + /** + * An optional title. + * Can be {@code null}. + */ + private String title; + + /** + * The content of the attachment. + * In case the media type is binary, this is a Base64 encoded string + */ + private final String content; + + private final MediaType mediaType; + + /** + * Convenience constructor, where title is set to {@code null} + */ + protected Attachment( String content, MediaType mediaType ) { + this( content, mediaType, null ); + } + + /** + * Creates a new instance of this Attachment + * @param content the content of this attachment. In case of a binary attachment, this must be + * Base64 encoded. Must not be {@code null} + * @param mediaType the mediaType. Must not be {@code null} + * @param title an optional title, may be {@code null} + */ + protected Attachment( String content, MediaType mediaType, String title ) { + if( mediaType == null ) { + throw new IllegalArgumentException( "MediaType must not be null" ); + } + + if( content == null ) { + throw new IllegalArgumentException( "Content must not be null" ); + } + + this.content = content; + this.mediaType = mediaType; + this.title = title; + } + + /** + * An optional title of the attachment. + * The title can be used by reporters, e.g. as a tooltip. + * Can be {@code null}. + */ + public String title() { + return title; + } + + /** + * The content of the attachment represented as a string. + * Binary attachments must be encoded in Base64 format. + */ + public String content() { + return content; + } + + /** + * The type of the attachment. + * It depends on the reporter how this information is used. + */ + public MediaType getMediaType() { + return mediaType; + } + + /** + * An optional title + */ + public String getTitle() { + return title; + } + + /** + * Sets the title and returns {@code this} + */ + public Attachment withTitle( String title ) { + this.title = title; + return this; + } + + /** + * Creates an attachment from a given array of bytes. + * The bytes will be Base64 encoded. + * @throws java.lang.IllegalArgumentException if mediaType is not binary + */ + public static Attachment fromBinaryBytes( byte[] bytes, MediaType mediaType ) { + if( !mediaType.isBinary() ) { + throw new IllegalArgumentException( "MediaType must be binary" ); + } + return new Attachment( DatatypeConverter.printBase64Binary( bytes ), mediaType, null ); + } + + /** + * Creates an attachment from a binary input stream. + * The content of the stream will be transformed into a Base64 encoded string + * @throws IOException if an I/O error occurs + * @throws java.lang.IllegalArgumentException if mediaType is not binary + */ + public static Attachment fromBinaryInputStream( InputStream inputStream, MediaType mediaType ) throws IOException { + return fromBinaryBytes( ByteStreams.toByteArray( inputStream ), mediaType ); + } + + /** + * Creates an attachment from the given binary file {@code file}. + * The content of the file will be transformed into a Base64 encoded string. + * @throws IOException if an I/O error occurs + * @throws java.lang.IllegalArgumentException if mediaType is not binary + */ + public static Attachment fromBinaryFile( File file, MediaType mediaType ) throws IOException { + FileInputStream stream = new FileInputStream( file ); + try { + return fromBinaryInputStream( stream, mediaType ); + } finally { + ResourceUtil.close( stream ); + } + } + + /** + * Creates a non-binary attachment from the given file. + * @throws IOException if an I/O error occurs + * @throws java.lang.IllegalArgumentException if mediaType is binary + */ + public static Attachment fromTextFile( File file, MediaType mediaType, Charset charSet ) throws IOException { + return fromText( Files.toString( file, charSet ), mediaType ); + } + + /** + * Creates a non-binary attachment from the given file. + * @throws IOException if an I/O error occurs + * @throws java.lang.IllegalArgumentException if mediaType is binary + */ + public static Attachment fromTextInputStream( InputStream inputStream, MediaType mediaType, Charset charset ) throws IOException { + return fromText( CharStreams.toString( new InputStreamReader( inputStream, charset ) ), mediaType ); + } + + /** + * Equivalent to {@link com.tngtech.jgiven.attachment.Attachment#Attachment(String, MediaType)} + * @throws java.lang.IllegalArgumentException if mediaType is binary + */ + public static Attachment fromText( String content, MediaType mediaType ) { + if( mediaType.isBinary() ) { + throw new IllegalArgumentException( "MediaType must not be binary" ); + } + return new Attachment( content, mediaType ); + } + + /** + * Equivalent to {@link com.tngtech.jgiven.attachment.Attachment#Attachment(String, MediaType)} + * @throws java.lang.IllegalArgumentException if mediaType is not binary + */ + public static Attachment fromBase64( String base64encodedContent, MediaType mediaType ) { + if( !mediaType.isBinary() ) { + throw new IllegalArgumentException( "MediaType must be binary" ); + } + return new Attachment( base64encodedContent, mediaType ); + } - String getMimeType(); } diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/BinaryAttachment.java b/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/BinaryAttachment.java deleted file mode 100644 index 422709eb3a..0000000000 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/BinaryAttachment.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.tngtech.jgiven.attachment; - -import javax.xml.bind.DatatypeConverter; - -/** - * Represents an binary attachment - */ -public class BinaryAttachment implements Attachment { - - private final String base64; - private final String mimeType; - - private BinaryAttachment( String base64, String mimeType ) { - this.base64 = base64; - this.mimeType = mimeType; - } - - public static BinaryAttachment fromBase64PngImage( String base64 ) { - return fromBase64( base64, "image/png" ); - } - - public static BinaryAttachment fromBase64( String base64, String mimeType ) { - return new BinaryAttachment( base64, mimeType ); - } - - public static BinaryAttachment fromBytes( byte[] bytes, String mimeType ) { - return new BinaryAttachment( DatatypeConverter.printBase64Binary( bytes ), mimeType ); - } - - @Override - public String asString() { - return base64; - } - - @Override - public String getMimeType() { - return mimeType; - } -} diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/MediaType.java b/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/MediaType.java new file mode 100644 index 0000000000..b8436d397d --- /dev/null +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/MediaType.java @@ -0,0 +1,118 @@ +package com.tngtech.jgiven.attachment; + +import static com.tngtech.jgiven.attachment.MediaType.Type.*; + +/** + * Represents a Media Type. + */ +public class MediaType { + + /** + * Represents the type of a Media Type + */ + public static enum Type { + APPLICATION( "application" ), + AUDIO( "audio" ), + IMAGE( "image" ), + TEXT( "text" ), + VIDEO( "video" ); + + private final String value; + + Type( String value ) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + + /** + * Get the type from a given string + */ + public Type fromString( String string ) { + for( Type type : values() ) { + if( type.value.equalsIgnoreCase( string ) ) { + return type; + } + } + throw new IllegalArgumentException( "Unknown type " + string ); + } + + } + + public static final MediaType PNG = image( "png" ); + public static final MediaType PLAIN_TEXT = text( "plain" ); + + private final Type type; + private final String subType; + private final boolean binary; + + /** + * Creates a new MediaType + * @param type the type + * @param subType the subtype + * @param binary whether or not content of this media type is binary. If {@code true}, the + * content will be encoded as Base64 when stored in the JSON model. + */ + public MediaType( Type type, String subType, boolean binary ) { + this.type = type; + this.subType = subType; + this.binary = binary; + } + + /** + * The type of the Media Type. + */ + public Type getType() { + return type; + } + + /** + * The subtype of the Media Type. + */ + public String getSubType() { + return subType; + } + + /** + * Whether this media type is binary or not. + */ + public boolean isBinary() { + return binary; + } + + public String asString() { + return type.value + "/" + subType; + } + + /** + * Creates a binary image media type with the given subtype. + */ + public static MediaType image( String subType ) { + return new MediaType( IMAGE, subType, true ); + } + + /** + * Creates a binary video media type with the given subtype. + */ + public static MediaType video( String subType ) { + return new MediaType( VIDEO, subType, true ); + } + + /** + * Creates a binary audio media type with the given subtype. + */ + public static MediaType audio( String subType ) { + return new MediaType( AUDIO, subType, true ); + } + + /** + * Creates a non-binary text media type with the given subtype. + */ + public static MediaType text( String subType ) { + return new MediaType( TEXT, subType, false ); + } + +} diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/TextAttachment.java b/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/TextAttachment.java deleted file mode 100644 index f334182f5c..0000000000 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/TextAttachment.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.tngtech.jgiven.attachment; - -/** - * Represents a text attachment - */ -public class TextAttachment implements Attachment { - - private final String content; - - public TextAttachment( String content ) { - this.content = content; - } - - @Override - public String asString() { - return content; - } - - @Override - public String getMimeType() { - return "text/plain"; - } -} diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/ScenarioExecutor.java b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/ScenarioExecutor.java index eea769abc7..356daf59eb 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/ScenarioExecutor.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/ScenarioExecutor.java @@ -20,7 +20,7 @@ import com.google.common.base.Optional; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.tngtech.jgiven.StepAccess; +import com.tngtech.jgiven.CurrentStep; import com.tngtech.jgiven.annotation.*; import com.tngtech.jgiven.attachment.Attachment; import com.tngtech.jgiven.exception.FailIfPassedException; @@ -78,7 +78,7 @@ public enum State { public ScenarioExecutor() { injector.injectValueByType( ScenarioExecutor.class, this ); - injector.injectValueByType( StepAccess.class, new StepAccessImpl() ); + injector.injectValueByType( CurrentStep.class, new StepAccessImpl() ); } static class StageState { @@ -91,21 +91,16 @@ static class StageState { } } - class StepAccessImpl implements StepAccess { + class StepAccessImpl implements CurrentStep { @Override public void addAttachment( Attachment attachment ) { listener.attachmentAdded( attachment ); } - @Override - public void setDescription( String description ) { - // listener.setDescription(description); - } - @Override public void setExtendedDescription( String extendedDescription ) { - // listener.setExtendedDescription(extendedDescription); + listener.extendedDescriptionUpdated( extendedDescription ); } } diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/NoOpScenarioListener.java b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/NoOpScenarioListener.java index bb626674c1..b29434680f 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/NoOpScenarioListener.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/NoOpScenarioListener.java @@ -34,4 +34,7 @@ public void scenarioFinished() {} @Override public void attachmentAdded( Attachment attachment ) {} + + @Override + public void extendedDescriptionUpdated( String extendedDescription ) {} } diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/ScenarioListener.java b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/ScenarioListener.java index 0418f078e0..b86bd8328e 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/ScenarioListener.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/ScenarioListener.java @@ -25,4 +25,6 @@ public interface ScenarioListener { void scenarioFinished(); void attachmentAdded( Attachment attachment ); + + void extendedDescriptionUpdated( String extendedDescription ); } diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/AttachmentModel.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/AttachmentModel.java index ac6c1d7cb2..6392f81b64 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/AttachmentModel.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/AttachmentModel.java @@ -1,8 +1,10 @@ package com.tngtech.jgiven.report.model; public class AttachmentModel { + private String title; private String value; - private String mimeType; + private String mediaType; + private boolean binary; public String getValue() { return value; @@ -12,11 +14,27 @@ public void setValue( String value ) { this.value = value; } - public String getMimeType() { - return mimeType; + public String getMediaType() { + return mediaType; } - public void setMimeType( String mimeType ) { - this.mimeType = mimeType; + public void setMediaType( String mimeType ) { + this.mediaType = mimeType; + } + + public void setTitle( String title ) { + this.title = title; + } + + public String getTitle() { + return title; + } + + public void setIsBinary( boolean isBinary ) { + this.binary = isBinary; + } + + public boolean isBinary() { + return binary; } } diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/ReportModelBuilder.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/ReportModelBuilder.java index 08599405cd..21e08431ce 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/ReportModelBuilder.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/ReportModelBuilder.java @@ -413,6 +413,11 @@ public void attachmentAdded( Attachment attachment ) { currentStep.setAttachment( attachment ); } + @Override + public void extendedDescriptionUpdated( String extendedDescription ) { + currentStep.setExtendedDescription( extendedDescription ); + } + public void setTestClass( Class> testClass ) { setClassName( testClass.getName() ); if( testClass.isAnnotationPresent( Description.class ) ) { diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/StepModel.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/StepModel.java index aef5c66108..c184f77f6d 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/StepModel.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/StepModel.java @@ -114,8 +114,10 @@ public Word getLastWord() { public void setAttachment( Attachment attachment ) { this.attachment = new AttachmentModel(); - this.attachment.setValue( attachment.asString() ); - this.attachment.setMimeType( attachment.getMimeType() ); + this.attachment.setTitle(attachment.getTitle()); + this.attachment.setValue( attachment.content() ); + this.attachment.setMediaType(attachment.getMediaType().asString()); + this.attachment.setIsBinary( attachment.getMediaType().isBinary()); } public AttachmentModel getAttachment() { diff --git a/jgiven-html5-report/src/app/index.html b/jgiven-html5-report/src/app/index.html index 231a4c2ae6..d35142bab5 100644 --- a/jgiven-html5-report/src/app/index.html +++ b/jgiven-html5-report/src/app/index.html @@ -259,7 +259,12 @@