package net.thucydides.core.model; import net.thucydides.core.annotations.Feature; import net.thucydides.core.model.features.ApplicationFeature; import net.thucydides.core.util.NameConverter; import static net.thucydides.core.model.ReportType.ROOT; /** * Represents a given user story or feature. * Used to record test results and so on. */ public class Story { private String id; private String storyName; private String storyClassName; private String path; private String narrative; private ApplicationFeature feature; private RequirementType type; public enum RequirementType { story, feature } protected Story(final Class<?> userStoryClass) { this.id = userStoryClass.getCanonicalName(); this.storyClassName = userStoryClass.getName(); this.storyName = NameConverter.humanize(userStoryClass.getSimpleName()); this.feature = findFeatureFrom(userStoryClass); this.path = pathOf(userStoryClass); this.type = RequirementType.story; } private String pathOf(Class<?> userStoryClass) { String canonicalName = userStoryClass.getCanonicalName(); int lastDot = canonicalName.lastIndexOf("."); if (lastDot > 0) { return canonicalName.substring(0, lastDot); } else { return ""; } } private ApplicationFeature findFeatureFrom(Class<?> userStoryClass) { if (getFeatureClass(userStoryClass) != null) { return ApplicationFeature.from(getFeatureClass(userStoryClass)); } return null; } public Story(String id, final String storyName, final String storyClassName, final String path, final ApplicationFeature feature) { this.id = id; this.storyName = storyName; this.storyClassName = storyClassName; this.feature = feature; this.path = path; this.narrative = null; this.type = RequirementType.story; } public Story(String id, final String storyName, final String storyClassName, final String path, final ApplicationFeature feature, final String narrative) { this(id, storyName,storyClassName, path, feature, narrative, RequirementType.story); } public Story(String id, final String storyName, final String storyClassName, final String path, final ApplicationFeature feature, final String narrative, final RequirementType type) { this.id = id; this.storyName = storyName; this.storyClassName = storyClassName; this.feature = feature; this.path = path; this.narrative = narrative; this.type = type; } protected Story(final String id, final String storyName, final ApplicationFeature feature, final String path) { this.id = id; this.storyName = storyName; this.storyClassName = null; this.feature = feature; this.path = path; this.type = RequirementType.story; } public String getId() { return id; } /** * Obtain a story instance from a given story class. * Story instances are used for recording and reporting test results. */ public static Story from(final Class<?> userStoryClass) { return new Story(userStoryClass); } /** * Create a story using a full class name as an id. * Note that the class may no longer exist, so the story needs to be able to exist beyond the life * of the original story class. This is used to deserialize stories from XML files. */ public static Story withId(final String storyId, final String storyName) { return new Story(storyId, storyName, null, null, null); } public Story withNarrative(String narrative) { return new Story(id, storyName, storyClassName, path, feature, narrative, type); } public static Story withIdAndPath(final String storyId, final String storyName, final String storyPath) { return new Story(storyId, storyName, null, storyPath, null); } public static Story called(final String storyName) { return new Story(storyName, storyName, null, null, null); } public static Story withId(final String storyId, final String storyName, final String featureClassName, final String featureName) { return new Story(storyId, storyName, new ApplicationFeature(featureClassName, featureName), null); } public static Story withIdAndPathAndFeature(final String storyId, final String storyName, String storyPath, final String featureClassName, final String featureName) { return new Story(storyId, storyName, new ApplicationFeature(featureClassName, featureName), storyPath); } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Story)) return false; Story story = (Story) o; if (!id.equals(story.id)) return false; if (path != null ? !path.equals(story.path) : story.path != null) return false; return true; } @Override public int hashCode() { int result = id.hashCode(); result = 31 * result + (path != null ? path.hashCode() : 0); return result; } /** * What feature does this story belong to? */ public static Class<?> getFeatureClass(Class<?> userStoryClass) { if (userStoryClass != null) { Class<?> enclosingClass = userStoryClass.getEnclosingClass(); if (isAFeature(enclosingClass)) { return enclosingClass; } } return null; } private static boolean isAFeature(Class<?> enclosingClass) { return (enclosingClass != null) && (enclosingClass.getAnnotation(Feature.class) != null); } /** * Returns the class representing the story that is tested by a given test class * This is indicated by the Story annotation. */ public static Class<?> testedInTestCase(Class<?> testClass) { net.thucydides.core.annotations.Story story = testClass.getAnnotation(net.thucydides.core.annotations.Story.class); if (story != null) { return story.value(); } else { return null; } } /** * Each story has a descriptive name. * This name is usually a human-readable version of the class name, or the story name for an easyb story. */ public String getName() { return storyName; } public String getStoryClassName() { return storyClassName; } /** * Find the name of the report for this story for the specified report type (XML, HTML,...). */ public String getReportName(final ReportType type) { return Stories.reportFor(this, type); } public String getReportName() { return getReportName(ROOT); } public ApplicationFeature getFeature() { return feature; } public String getPath() { return path; } public String getStoryName() { return storyName; } public String getNarrative() { return narrative; } public RequirementType getType() { return type; } public Story withPath(String storyPath) { return new Story(this.id, this.storyName, this.feature, storyPath); } public Story asFeature() { return new Story(this.id, this.storyName, this.storyClassName, this.path,this.feature,this.narrative, RequirementType.feature); } public TestTag asTag() { return TestTag.withName(storyName).andType(type.toString()); } }