Skip to content

Commit

Permalink
Merge pull request #213 from Airblader/feature-172
Browse files Browse the repository at this point in the history
Allow adding tags dynamically and also allow tags on Stage classes and Stage methods
  • Loading branch information
janschaefer authored Jul 7, 2016
2 parents ce60dfd + 605264a commit e162dcf
Show file tree
Hide file tree
Showing 16 changed files with 418 additions and 77 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
* Added a new comment() method to provide further information on specific step method invocations, see [#50](https://github.com/TNG/JGiven/issues/50).
* Steps can now have multiple attachments [#194](https://github.com/TNG/JGiven/issues/194).
* Tags can now be hidden from the navigation bar in the HTML report by setting the `showInNavigation` attribute to `false` [#211](https://github.com/TNG/JGiven/issues/211).
* Added a new CurrentScenario interface similar to CurrentStep.
* The CurrentScenario interface allows adding tags programmatically, see [#172](https://github.com/TNG/JGiven/issues/172).
* Allow tag annotations on step methods and step classes.

## Breaking Changes in the JSON model

Expand Down
23 changes: 23 additions & 0 deletions jgiven-core/src/main/java/com/tngtech/jgiven/CurrentScenario.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.tngtech.jgiven;

import java.lang.annotation.Annotation;

import com.tngtech.jgiven.annotation.ScenarioState;

/**
* This interface can be injected into a stage using the {@link ScenarioState} annotation.
* It provided programmatic access to the current scenario.
*
* @since 0.12.0
*/
public interface CurrentScenario {

/**
* Dynamically add a tag to the scenario.
* @param annotationClass The tag annotation class to use.
* @param values List of custom values.
* @since 0.12.0
*/
void addTag( Class<? extends Annotation> annotationClass, String... values );

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,28 @@
* Is used as an attribute of the {@link com.tngtech.jgiven.annotation.IsTag} annotation
* to dynamically generate a description for an annotation depending on its value.
* <p>
* Implementations of this interface must be a public non-abstract class that is not a non-static inner class
* Implementations of this interface must be a public non-abstract class that is not a non-static inner class
* and must have a public default constructor.
* </p>
*
*
* @since 0.6.3
*/
public interface TagDescriptionGenerator {

/**
* Implement this method to generate the description for the given annotation and its value.
* <p>
* Note that when the value of the annotation is an array and {@link com.tngtech.jgiven.annotation.IsTag#explodeArray()}
* Note that when the value of the annotation is an array and {@link com.tngtech.jgiven.annotation.IsTag#explodeArray()}
* is {@code true}, then this method is called for each value of the array and not once for the whole array.
* Otherwise it is called only once.
* </p>
* @param tagConfiguration the configuration of the tag. The values typically correspond to the {@link IsTag annotation}.
* @param tagConfiguration the configuration of the tag. The values typically correspond to the {@link IsTag annotation}.
* However, it is also possible to configure annotations to be tags using {@link com.tngtech.jgiven.annotation.JGivenConfiguration},
* in which case there is no {@link IsTag} annotation.
* @param annotation the actual annotation that was used as a tag
* @param annotation the actual annotation that was used as a tag. Note that this can be {@code null} in the case of
* dynamically added tags.
* @param value the value of the annotation. If the annotation has no value the default value is passed ({@link com.tngtech.jgiven.annotation.IsTag#value()}
*
*
* @return the description of the annotation for the given value
*/
String generateDescription( TagConfiguration tagConfiguration, Annotation annotation, Object value );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ public interface TagHrefGenerator {
* @param tagConfiguration the configuration of the tag. The values typically correspond to the {@link IsTag annotation}.
* However, it is also possible to configure annotations to be tags using {@link JGivenConfiguration},
* in which case there is no {@link IsTag} annotation.
* @param annotation the actual annotation that was used as a tag
* @param annotation the actual annotation that was used as a tag. Note that this can be {@code null} in the case of
* dynamically added tags.
* @param value the value of the annotation. If the annotation has no value the default value is passed ({@link IsTag#value()}
*
*
* @return the description of the annotation for the given value
*/
String generateHref( TagConfiguration tagConfiguration, Annotation annotation, Object value );
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.tngtech.jgiven.impl;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.tngtech.jgiven.impl;

import com.google.common.base.CaseFormat;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.tngtech.jgiven.annotation.*;
import com.tngtech.jgiven.attachment.Attachment;
Expand Down Expand Up @@ -172,6 +174,9 @@ public void stepMethodInvoked( Method method, List<NamedArgument> arguments, Inv
} else if( method.isAnnotationPresent( StepComment.class ) ) {
stepCommentAdded( arguments );
} else {
addTags( method.getAnnotations() );
addTags( method.getDeclaringClass().getAnnotations() );

addStepMethod( method, arguments, mode, hasNestedSteps );
}
}
Expand Down Expand Up @@ -381,26 +386,35 @@ private void readAnnotations( Class<?> testClass, Method method ) {

public void addTags( Annotation... annotations ) {
for( Annotation annotation : annotations ) {
List<Tag> tags = toTags( annotation );
addTags( toTags( annotation ) );
}
}

private void addTags( List<Tag> tags ) {
if( tags.isEmpty() ) {
return;
}

if( reportModel != null ) {
this.reportModel.addTags( tags );
}

if( scenarioModel != null ) {
this.scenarioModel.addTags( tags );
}
}

public List<Tag> toTags( Annotation annotation ) {
Class<? extends Annotation> annotationType = annotation.annotationType();
IsTag isTag = annotationType.getAnnotation( IsTag.class );
TagConfiguration tagConfig;
if( isTag != null ) {
tagConfig = fromIsTag( isTag, annotation );
} else {
tagConfig = configuration.getTagConfiguration( annotationType );
}

TagConfiguration tagConfig = toTagConfiguration( annotationType );
if( tagConfig == null ) {
return Collections.emptyList();
}

return toTags( tagConfig, Optional.of( annotation ) );
}

private List<Tag> toTags( TagConfiguration tagConfig, Optional<Annotation> annotation ) {
Tag tag = new Tag( tagConfig.getAnnotationType() );

if( !Strings.isNullOrEmpty( tagConfig.getName() ) ) {
Expand Down Expand Up @@ -430,55 +444,74 @@ public List<Tag> toTags( Annotation annotation ) {
tag.setValue( tagConfig.getDefaultValue() );
}

if( tagConfig.isIgnoreValue() ) {
tag.setDescription( getDescriptionFromGenerator( tagConfig, annotation, value ) );
tag.setTags( tagConfig.getTags() );

if( tagConfig.isIgnoreValue() || !annotation.isPresent() ) {
tag.setDescription( getDescriptionFromGenerator( tagConfig, annotation.orNull(), tagConfig.getDefaultValue() ) );
tag.setHref( getHref( tagConfig, annotation.orNull(), value ) );

return Arrays.asList( tag );
}

tag.setTags( tagConfig.getTags() );

try {
Method method = annotationType.getMethod( "value" );
value = method.invoke( annotation );
Method method = annotation.get().annotationType().getMethod( "value" );
value = method.invoke( annotation.get() );
if( value != null ) {
if( value.getClass().isArray() ) {
Object[] objectArray = (Object[]) value;
if( tagConfig.isExplodeArray() ) {
List<Tag> explodedTags = getExplodedTags( tag, objectArray, annotation, tagConfig );
List<Tag> explodedTags = getExplodedTags( tag, objectArray, annotation.get(), tagConfig );
return explodedTags;
}
tag.setValue( toStringList( objectArray ) );

} else {
tag.setValue( String.valueOf( value ) );
}
}
} catch( NoSuchMethodException ignore ) {

} catch( Exception e ) {
log.error( "Error while getting 'value' method of annotation " + annotation, e );
log.error( "Error while getting 'value' method of annotation " + annotation.get(), e );
}

tag.setDescription( getDescriptionFromGenerator( tagConfig, annotation, value ) );
tag.setHref( getHref( tagConfig, annotation, value ) );
tag.setDescription( getDescriptionFromGenerator( tagConfig, annotation.get(), value ) );
tag.setHref( getHref( tagConfig, annotation.get(), value ) );

return Arrays.asList( tag );
}

public TagConfiguration fromIsTag( IsTag isTag, Annotation annotation ) {

String name = Strings.isNullOrEmpty( isTag.name() ) ? isTag.type() : isTag.name();

return TagConfiguration.builder( annotation.annotationType() ).defaultValue( isTag.value() ).description( isTag.description() )
.explodeArray( isTag.explodeArray() ).ignoreValue( isTag.ignoreValue() ).prependType( isTag.prependType() ).name( name )
.descriptionGenerator( isTag.descriptionGenerator() ).cssClass( isTag.cssClass() ).color( isTag.color() )
.style( isTag.style() ).tags( getTagNames( isTag, annotation ) )
.href( isTag.href() ).hrefGenerator( isTag.hrefGenerator() )
.showInNavigation( isTag.showInNavigation() ).build();
private TagConfiguration toTagConfiguration( Class<? extends Annotation> annotationType ) {
IsTag isTag = annotationType.getAnnotation( IsTag.class );
if( isTag != null ) {
return fromIsTag( isTag, annotationType );
}

return configuration.getTagConfiguration( annotationType );
}

private List<String> getTagNames( IsTag isTag, Annotation annotation ) {
List<Tag> tags = getTags( isTag, annotation );
public TagConfiguration fromIsTag( IsTag isTag, Class<? extends Annotation> annotationType ) {
String name = Strings.isNullOrEmpty( isTag.name() ) ? isTag.type() : isTag.name();

return TagConfiguration.builder( annotationType )
.defaultValue( isTag.value() )
.description( isTag.description() )
.explodeArray( isTag.explodeArray() )
.ignoreValue( isTag.ignoreValue() )
.prependType( isTag.prependType() )
.name( name )
.descriptionGenerator( isTag.descriptionGenerator() )
.cssClass( isTag.cssClass() )
.color( isTag.color() )
.style( isTag.style() )
.tags( getTagNames( isTag, annotationType ) )
.href( isTag.href() )
.hrefGenerator( isTag.hrefGenerator() )
.showInNavigation( isTag.showInNavigation() )
.build();
}

private List<String> getTagNames( IsTag isTag, Class<? extends Annotation> annotationType ) {
List<Tag> tags = getTags( isTag, annotationType );
reportModel.addTags( tags );
List<String> tagNames = Lists.newArrayList();
for( Tag tag : tags ) {
Expand All @@ -487,10 +520,10 @@ private List<String> getTagNames( IsTag isTag, Annotation annotation ) {
return tagNames;
}

private List<Tag> getTags( IsTag isTag, Annotation annotation ) {
private List<Tag> getTags( IsTag isTag, Class<? extends Annotation> annotationType ) {
List<Tag> allTags = Lists.newArrayList();

for( Annotation a : annotation.annotationType().getAnnotations() ) {
for( Annotation a : annotationType.getAnnotations() ) {
if( a.annotationType().isAnnotationPresent( IsTag.class ) ) {
List<Tag> tags = toTags( a );
for( Tag tag : tags ) {
Expand Down Expand Up @@ -571,6 +604,25 @@ public void sectionAdded( String sectionTitle ) {
getCurrentScenarioCase().addStep( stepModel );
}

@Override
public void tagAdded( Class<? extends Annotation> annotationClass, String... values ) {
TagConfiguration tagConfig = toTagConfiguration( annotationClass );
if( tagConfig == null ) {
return;
}

List<Tag> tags = toTags( tagConfig, Optional.<Annotation>absent() );
if( tags.isEmpty() ) {
return;
}

if( values.length > 0 ) {
addTags( getExplodedTags( Iterables.getOnlyElement( tags ), values, null, tagConfig ) );
} else {
addTags( tags );
}
}

public ReportModel getReportModel() {
return reportModel;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.tngtech.jgiven.CurrentScenario;
import com.tngtech.jgiven.CurrentStep;
import com.tngtech.jgiven.annotation.AfterScenario;
import com.tngtech.jgiven.annotation.AfterStage;
Expand Down Expand Up @@ -80,6 +81,7 @@ public class StandaloneScenarioExecutor implements ScenarioExecutor {
public StandaloneScenarioExecutor() {
injector.injectValueByType( StandaloneScenarioExecutor.class, this );
injector.injectValueByType( CurrentStep.class, new StepAccessImpl() );
injector.injectValueByType( CurrentScenario.class, new ScenarioAccessImpl() );
}

protected static class StageState {
Expand All @@ -106,6 +108,15 @@ public void setExtendedDescription( String extendedDescription ) {
}
}

class ScenarioAccessImpl implements CurrentScenario {

@Override
public void addTag( Class<? extends Annotation> annotationClass, String... values ) {
listener.tagAdded( annotationClass, values );
}

}

class MethodHandler implements StepMethodHandler {
@Override
public void handleMethod( Object stageInstance, Method paramMethod, Object[] arguments, InvocationMode mode,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.tngtech.jgiven.impl.intercept;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;

Expand Down Expand Up @@ -43,7 +44,8 @@ public void attachmentAdded( Attachment attachment ) {}
public void extendedDescriptionUpdated( String extendedDescription ) {}

@Override
public void sectionAdded( String sectionTitle ) {
public void sectionAdded( String sectionTitle ) {}

}
@Override
public void tagAdded( Class<? extends Annotation> annotationClass, String... values ) {}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.tngtech.jgiven.impl.intercept;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;

Expand Down Expand Up @@ -32,4 +33,6 @@ public interface ScenarioListener {
void extendedDescriptionUpdated( String extendedDescription );

void sectionAdded( String sectionTitle );

void tagAdded( Class<? extends Annotation> annotationClass, String... values );
}
Loading

0 comments on commit e162dcf

Please sign in to comment.