├── doc ├── images │ ├── info-block.png │ ├── ditaa-support.png │ ├── warning-block.png │ └── jlatexmath-support.png └── sample-yamex-report.pdf ├── tzatziki-core └── src │ ├── test │ ├── resources │ │ ├── test-settings.properties │ │ ├── tzatziki │ │ │ ├── analysis │ │ │ │ ├── exec │ │ │ │ │ ├── gson │ │ │ │ │ │ ├── steps-w-embedding.feature │ │ │ │ │ │ └── comments-n-description-interleaved.feature │ │ │ │ │ └── feature │ │ │ │ │ │ └── feature-w-outline.feature │ │ │ │ └── step │ │ │ │ │ ├── subdomain │ │ │ │ │ ├── extra-hot.feature │ │ │ │ │ └── running-out.feature │ │ │ │ │ └── going-into-business.feature │ │ │ └── junit │ │ │ │ └── coffeemachine │ │ │ │ ├── 03-extra-hot.feature │ │ │ │ ├── 05-running-out.feature │ │ │ │ ├── 01-making-drinks.feature │ │ │ │ ├── 04-making-money.feature │ │ │ │ └── 02-going-into-business.feature │ │ └── logback-test.xml │ └── java │ │ └── tzatziki │ │ ├── analysis │ │ ├── exec │ │ │ ├── tag │ │ │ │ ├── TagFilterTest.java │ │ │ │ └── TagViewTest.java │ │ │ └── gson │ │ │ │ └── JsonIOTest.java │ │ └── java │ │ │ ├── MethodEntryTest.java │ │ │ └── stepdefs │ │ │ └── OptionStepdefs.java │ │ ├── util │ │ ├── ExceptionUtilsTest.java │ │ ├── PackagePathTest.java │ │ ├── LoadJson.java │ │ ├── MemoizableIteratorTest.java │ │ └── JsonPath.java │ │ ├── TestSettings.java │ │ └── exploratory │ │ └── cucumber │ │ └── TagExpressionTest.java │ └── main │ └── java │ └── tzatziki │ ├── util │ ├── Filter.java │ ├── Consumer.java │ ├── New.java │ ├── Equal.java │ ├── ExceptionUtils.java │ ├── MemoizableIterator.java │ ├── PackagePath.java │ ├── PropertiesLoader.java │ └── Filters.java │ └── analysis │ ├── check │ ├── TagChecker.java │ ├── CucumberPart.java │ ├── CheckAtLeastOneTagsExist.java │ └── CheckAllTagsExist.java │ ├── exec │ ├── model │ │ ├── HasTags.java │ │ ├── HasComments.java │ │ ├── Status.java │ │ ├── LineRange.java │ │ ├── BackgroundExec.java │ │ ├── ExamplesRow.java │ │ ├── Embedded.java │ │ ├── EmbeddingAndWriteContainer.java │ │ ├── ScenarioRef.java │ │ ├── MatchExec.java │ │ ├── DataTable.java │ │ ├── ScenarioExec.java │ │ ├── ExamplesExec.java │ │ └── StepContainer.java │ ├── tag │ │ ├── TagExpressionPredicate.java │ │ ├── Tags.java │ │ └── TagFilter.java │ ├── gson │ │ ├── ScenarioExecSerializer.java │ │ ├── ScenarioOutlineExecSerializer.java │ │ ├── JsonIO.java │ │ ├── StepContainerDeserializer.java │ │ └── JsonEmitterReport.java │ └── ExecutionFilter.java │ ├── java │ ├── Describable.java │ ├── KeywordBasedPattern.java │ ├── GrammarVisitor.java │ ├── Parameter.java │ ├── GrammarParserListener.java │ ├── GrammarParserStatisticsListener.java │ ├── GrammarParserListenerAdapter.java │ ├── UsedBy.java │ ├── HumanReadableRegex.java │ ├── ClassEntry.java │ ├── ConsoleOutputListener.java │ └── Grammar.java │ ├── step │ ├── Background.java │ ├── FeatureVisitor.java │ ├── FeatureVisitorAdapter.java │ ├── Features.java │ ├── Step.java │ ├── Scenario.java │ ├── ScenarioOutline.java │ ├── TagCollector.java │ ├── Feature.java │ └── FeatureParser.java │ ├── tag │ ├── Tag.java │ ├── TagDictionaryLoader.java │ └── TagDictionary.java │ └── GrammarConsolidation.java ├── tzatziki-pdf └── src │ ├── test │ ├── resources │ │ ├── test-settings.properties │ │ ├── tzatziki │ │ │ └── pdf │ │ │ │ ├── images │ │ │ │ ├── coins.jpeg │ │ │ │ ├── customer.jpeg │ │ │ │ ├── coffee-cup.jpeg │ │ │ │ ├── coffee-800x700.png │ │ │ │ └── flickr.com-photos-audcrane.png │ │ │ │ ├── tags.properties │ │ │ │ └── feature │ │ │ │ └── 01-sample.feature │ │ └── logback-test.xml │ └── java │ │ └── tzatziki │ │ └── pdf │ │ ├── feature │ │ └── RunFeature.java │ │ └── TestSettings.java │ └── main │ └── java │ └── tzatziki │ └── pdf │ ├── support │ └── StylesPostProcessor.java │ ├── Settings.java │ ├── model │ ├── Tags.java │ ├── Steps.java │ └── ScenarioOutlineWithResolved.java │ ├── Comments.java │ ├── Margin.java │ ├── emitter │ ├── EmbeddedEmitter.java │ ├── TagsEmitter.java │ ├── DefaultPdfEmitters.java │ ├── StatusMarker.java │ ├── ScenarioEmitter.java │ └── StepContainerEmitter.java │ └── EmitterContext.java ├── tzatziki-samples └── src │ ├── test │ ├── resources │ │ ├── test-settings.properties │ │ └── logback-test.xml │ └── java │ │ └── samples │ │ ├── coffeemachine │ │ ├── RunAllCucumberTest.java │ │ ├── CoffeeMachineFeatureTest.java │ │ ├── CoffeeMachineWipFeatureTest.java │ │ ├── CoffeeMachineTagCheckTest.java │ │ └── CoffeeMachineDrinkTagCheckTest.java │ │ └── TestSettings.java │ └── main │ ├── resources │ └── samples │ │ ├── dinovet │ │ ├── README.md │ │ └── features │ │ │ ├── view_patient_history.feature │ │ │ └── manage_diagnosis_events.feature │ │ ├── stylius │ │ ├── README.md │ │ ├── frontend │ │ │ ├── homepage.feature │ │ │ ├── user_login_via_oauth.feature │ │ │ ├── account_homepage.feature │ │ │ ├── currencies.feature │ │ │ ├── cart_inclusive_tax.feature │ │ │ ├── checkout_finalize.feature │ │ │ ├── user_login.feature │ │ │ ├── checkout_addressing.feature │ │ │ ├── account_password.feature │ │ │ ├── checkout_taxation.feature │ │ │ ├── checkout_payment.feature │ │ │ ├── products.feature │ │ │ ├── cart_promotions_dates.feature │ │ │ ├── cart_tax_categories.feature │ │ │ ├── user_registration.feature │ │ │ ├── cart_promotions_complex.feature │ │ │ └── checkout_start.feature │ │ └── backend │ │ │ ├── taxation_settings.feature │ │ │ ├── dashboard.feature │ │ │ └── products_filter.feature │ │ ├── coffeemachine │ │ ├── images │ │ │ ├── coins.jpeg │ │ │ ├── customer.jpeg │ │ │ ├── coffee-cup.jpeg │ │ │ ├── coffee-800x700.png │ │ │ └── flickr.com-photos-audcrane.png │ │ ├── 00-meta.properties │ │ ├── tags.properties │ │ ├── 03-extra-hot.feature │ │ ├── 05-running-out.feature │ │ ├── 06-background.feature │ │ ├── 01-making-drinks.feature │ │ ├── 04-making-money.feature │ │ └── 02-going-into-business.feature │ │ ├── sample │ │ ├── game-of-life.feature │ │ └── countries.feature │ │ └── game-of-life │ │ ├── board.feature │ │ └── game-of-life.feature │ └── java │ └── samples │ └── coffeemachine │ ├── DrinkMaker.java │ ├── Hooks.java │ ├── MoneySteps.java │ ├── Context.java │ ├── Gateway.java │ └── TakeOrderSteps.java ├── tzatziki-web ├── src │ └── main │ │ └── java │ │ └── tzatziki │ │ └── web │ │ ├── ScenarioDAO.java │ │ ├── GrammarDAO.java │ │ ├── ScenarioResource.java │ │ ├── AppConfiguration.java │ │ ├── GrammarDAOHealthCheck.java │ │ ├── ScenarioDAOHealthCheck.java │ │ ├── GrammarResource.java │ │ └── App.java └── conf │ └── config.yml ├── .gitignore ├── release.md └── LICENSE /doc/images/info-block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arnauld/tzatziki/HEAD/doc/images/info-block.png -------------------------------------------------------------------------------- /doc/images/ditaa-support.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arnauld/tzatziki/HEAD/doc/images/ditaa-support.png -------------------------------------------------------------------------------- /doc/images/warning-block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arnauld/tzatziki/HEAD/doc/images/warning-block.png -------------------------------------------------------------------------------- /doc/sample-yamex-report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arnauld/tzatziki/HEAD/doc/sample-yamex-report.pdf -------------------------------------------------------------------------------- /doc/images/jlatexmath-support.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arnauld/tzatziki/HEAD/doc/images/jlatexmath-support.png -------------------------------------------------------------------------------- /tzatziki-core/src/test/resources/test-settings.properties: -------------------------------------------------------------------------------- 1 | buildDir=${project.build.directory} 2 | baseDir=${project.basedir} -------------------------------------------------------------------------------- /tzatziki-pdf/src/test/resources/test-settings.properties: -------------------------------------------------------------------------------- 1 | buildDir=${project.build.directory} 2 | baseDir=${project.basedir} -------------------------------------------------------------------------------- /tzatziki-samples/src/test/resources/test-settings.properties: -------------------------------------------------------------------------------- 1 | buildDir=${project.build.directory} 2 | baseDir=${project.basedir} -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/dinovet/README.md: -------------------------------------------------------------------------------- 1 | Copied from [dinovet](https://github.com/dinocore/dinovet/tree/master/features) 2 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/stylius/README.md: -------------------------------------------------------------------------------- 1 | Copied from [Stylius-test](https://github.com/lensky84/sylius-test/tree/master/features) 2 | -------------------------------------------------------------------------------- /tzatziki-pdf/src/test/resources/tzatziki/pdf/images/coins.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arnauld/tzatziki/HEAD/tzatziki-pdf/src/test/resources/tzatziki/pdf/images/coins.jpeg -------------------------------------------------------------------------------- /tzatziki-pdf/src/test/resources/tzatziki/pdf/images/customer.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arnauld/tzatziki/HEAD/tzatziki-pdf/src/test/resources/tzatziki/pdf/images/customer.jpeg -------------------------------------------------------------------------------- /tzatziki-pdf/src/test/resources/tzatziki/pdf/images/coffee-cup.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arnauld/tzatziki/HEAD/tzatziki-pdf/src/test/resources/tzatziki/pdf/images/coffee-cup.jpeg -------------------------------------------------------------------------------- /tzatziki-pdf/src/test/resources/tzatziki/pdf/images/coffee-800x700.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arnauld/tzatziki/HEAD/tzatziki-pdf/src/test/resources/tzatziki/pdf/images/coffee-800x700.png -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/coffeemachine/images/coins.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arnauld/tzatziki/HEAD/tzatziki-samples/src/main/resources/samples/coffeemachine/images/coins.jpeg -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/coffeemachine/images/customer.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arnauld/tzatziki/HEAD/tzatziki-samples/src/main/resources/samples/coffeemachine/images/customer.jpeg -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/coffeemachine/images/coffee-cup.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arnauld/tzatziki/HEAD/tzatziki-samples/src/main/resources/samples/coffeemachine/images/coffee-cup.jpeg -------------------------------------------------------------------------------- /tzatziki-pdf/src/test/resources/tzatziki/pdf/images/flickr.com-photos-audcrane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arnauld/tzatziki/HEAD/tzatziki-pdf/src/test/resources/tzatziki/pdf/images/flickr.com-photos-audcrane.png -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/coffeemachine/images/coffee-800x700.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arnauld/tzatziki/HEAD/tzatziki-samples/src/main/resources/samples/coffeemachine/images/coffee-800x700.png -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/util/Filter.java: -------------------------------------------------------------------------------- 1 | package tzatziki.util; 2 | 3 | /** 4 | * @author @aloyer 5 | */ 6 | public interface Filter { 7 | T filter(T value); 8 | } 9 | -------------------------------------------------------------------------------- /tzatziki-web/src/main/java/tzatziki/web/ScenarioDAO.java: -------------------------------------------------------------------------------- 1 | package tzatziki.web; 2 | 3 | /** 4 | * @author @aloyer 5 | */ 6 | public interface ScenarioDAO { 7 | void check(); 8 | } 9 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/util/Consumer.java: -------------------------------------------------------------------------------- 1 | package tzatziki.util; 2 | 3 | /** 4 | * @author @aloyer 5 | */ 6 | public interface Consumer { 7 | void consume(T value); 8 | } 9 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/coffeemachine/00-meta.properties: -------------------------------------------------------------------------------- 1 | version=Version ${project.version} 2 | image-root-path=${project.basedir}/src/test/resources/sample/coffeemachine/images 3 | working-dir=${pom.build.directory} -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/coffeemachine/images/flickr.com-photos-audcrane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arnauld/tzatziki/HEAD/tzatziki-samples/src/main/resources/samples/coffeemachine/images/flickr.com-photos-audcrane.png -------------------------------------------------------------------------------- /tzatziki-samples/src/main/java/samples/coffeemachine/DrinkMaker.java: -------------------------------------------------------------------------------- 1 | package samples.coffeemachine; 2 | 3 | /** 4 | * @author @aloyer 5 | */ 6 | public interface DrinkMaker { 7 | void executeCommand(String command); 8 | } 9 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/check/TagChecker.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.check; 2 | 3 | import java.util.List; 4 | 5 | import tzatziki.analysis.tag.TagDictionary; 6 | 7 | public interface TagChecker { 8 | void evaluate(TagDictionary dictionary, List tags); 9 | } -------------------------------------------------------------------------------- /tzatziki-web/src/main/java/tzatziki/web/GrammarDAO.java: -------------------------------------------------------------------------------- 1 | package tzatziki.web; 2 | 3 | import tzatziki.analysis.java.Grammar; 4 | 5 | /** 6 | * @author @aloyer 7 | */ 8 | public interface GrammarDAO { 9 | Grammar getGrammar(); 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | *.pdf 4 | *.pdf.tmp 5 | 6 | .DS_Store 7 | 8 | # Package Files # 9 | *.jar 10 | *.war 11 | *.ear 12 | tmp/ 13 | target/ 14 | cucumber-contrib.iml 15 | .classpath 16 | .project 17 | .settings/ 18 | .idea/ 19 | *.iml 20 | .sass-cache/ 21 | pom.xml.versionsBackup 22 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/check/CucumberPart.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.check; 2 | 3 | /** 4 | * Cucumber part to check 5 | * @author pverdage 6 | * 7 | */ 8 | public enum CucumberPart { 9 | Feature, 10 | Scenario, 11 | ScenarioOutline; 12 | 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/util/New.java: -------------------------------------------------------------------------------- 1 | package tzatziki.util; 2 | 3 | import java.util.Stack; 4 | 5 | /** 6 | * @author @aloyer 7 | */ 8 | public class New { 9 | public static Stack newStack() { 10 | return new Stack(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tzatziki-pdf/src/main/java/tzatziki/pdf/support/StylesPostProcessor.java: -------------------------------------------------------------------------------- 1 | package tzatziki.pdf.support; 2 | 3 | import gutenberg.itext.Styles; 4 | 5 | /** 6 | * @author @aloyer 7 | */ 8 | public interface StylesPostProcessor { 9 | void postProcess(Styles styles); 10 | } 11 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/exec/model/HasTags.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec.model; 2 | 3 | import com.google.common.collect.FluentIterable; 4 | 5 | /** 6 | * @author @aloyer 7 | */ 8 | public interface HasTags { 9 | FluentIterable tags(); 10 | } 11 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/java/samples/coffeemachine/Hooks.java: -------------------------------------------------------------------------------- 1 | package samples.coffeemachine; 2 | 3 | import cucumber.api.java.Before; 4 | 5 | /** 6 | * @author @aloyer 7 | */ 8 | public class Hooks { 9 | 10 | @Before 11 | public void setUpMocks() { 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/exec/model/HasComments.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec.model; 2 | 3 | import com.google.common.collect.FluentIterable; 4 | 5 | /** 6 | * @author @aloyer 7 | */ 8 | public interface HasComments { 9 | FluentIterable comments(); 10 | } 11 | -------------------------------------------------------------------------------- /tzatziki-web/src/main/java/tzatziki/web/ScenarioResource.java: -------------------------------------------------------------------------------- 1 | package tzatziki.web; 2 | 3 | /** 4 | * @author @aloyer 5 | */ 6 | public class ScenarioResource { 7 | private final ScenarioDAO scenarioDAO; 8 | 9 | public ScenarioResource(ScenarioDAO scenarioDAO) { 10 | this.scenarioDAO = scenarioDAO; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tzatziki-pdf/src/main/java/tzatziki/pdf/Settings.java: -------------------------------------------------------------------------------- 1 | package tzatziki.pdf; 2 | 3 | /** 4 | * @author @aloyer 5 | */ 6 | public class Settings { 7 | public static final String PRIMARY_COLOR = "primary-color"; 8 | public static final String EMPHASIZE_COLOR = "emphasize-color"; 9 | public static final String META_FONT = "meta-font"; 10 | } 11 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/stylius/frontend/homepage.feature: -------------------------------------------------------------------------------- 1 | @homepage 2 | Feature: Store homepage 3 | In order to access and browse the store 4 | As a visitor 5 | I want to be able to see the homepage 6 | 7 | Scenario: Viewing the homepage at website root 8 | When I go to the website root 9 | Then I should be on the homepage 10 | And I should see "Welcome to Sylius" 11 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/java/Describable.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.java; 2 | 3 | /** 4 | * @author @aloyer 5 | */ 6 | public class Describable { 7 | private String comment; 8 | 9 | public void describeWith(String comment) { 10 | this.comment = comment; 11 | } 12 | 13 | public String comment() { 14 | return comment; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/step/Background.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.step; 2 | 3 | import com.google.common.collect.Lists; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author @aloyer 9 | */ 10 | public class Background { 11 | private List stepList = Lists.newArrayList(); 12 | 13 | public void add(Step step) { 14 | stepList.add(step); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/util/Equal.java: -------------------------------------------------------------------------------- 1 | package tzatziki.util; 2 | 3 | /** 4 | * @author @aloyer 5 | */ 6 | public class Equal { 7 | public static boolean areEquals(Object strOne, Object strTwo) { 8 | if (strOne == null) { 9 | return strTwo == null; 10 | } else { 11 | return strTwo != null && strOne.equals(strTwo); 12 | } 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/dinovet/features/view_patient_history.feature: -------------------------------------------------------------------------------- 1 | Feature: View Patient History 2 | In order to safely treat patients 3 | An employee 4 | needs to be able to view a patient's medical history 5 | 6 | Scenario: View all patient history 7 | Given I have added a client and patient 8 | And I have made a diagnosis 9 | When I go to the patient events page 10 | Then I should see a list of events 11 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/java/samples/coffeemachine/MoneySteps.java: -------------------------------------------------------------------------------- 1 | package samples.coffeemachine; 2 | 3 | import cucumber.api.java.en.Given; 4 | 5 | import java.math.BigDecimal; 6 | 7 | /** 8 | * @author @aloyer 9 | */ 10 | public class MoneySteps { 11 | 12 | 13 | @Given("^I've inserted (\\d+)€ in the machine$") 14 | public void I_ve_inserted_€_in_the_machine(BigDecimal amount) throws Throwable { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tzatziki-pdf/src/test/java/tzatziki/pdf/feature/RunFeature.java: -------------------------------------------------------------------------------- 1 | package tzatziki.pdf.feature; 2 | 3 | import cucumber.api.CucumberOptions; 4 | import cucumber.api.junit.Cucumber; 5 | import org.junit.runner.RunWith; 6 | 7 | /** 8 | * @author @aloyer 9 | */ 10 | @RunWith(Cucumber.class) 11 | @CucumberOptions( 12 | format = "tzatziki.analysis.exec.gson.JsonEmitterReport:target/tz-pdf") 13 | public class RunFeature { 14 | } 15 | -------------------------------------------------------------------------------- /tzatziki-pdf/src/main/java/tzatziki/pdf/model/Tags.java: -------------------------------------------------------------------------------- 1 | package tzatziki.pdf.model; 2 | 3 | import com.google.common.collect.FluentIterable; 4 | 5 | /** 6 | * @author @aloyer 7 | */ 8 | public class Tags { 9 | private final FluentIterable tags; 10 | 11 | public Tags(FluentIterable tags) { 12 | this.tags = tags; 13 | } 14 | 15 | public FluentIterable tags() { 16 | return tags; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tzatziki-samples/src/test/java/samples/coffeemachine/RunAllCucumberTest.java: -------------------------------------------------------------------------------- 1 | package samples.coffeemachine; 2 | 3 | import cucumber.api.CucumberOptions; 4 | import cucumber.api.junit.Cucumber; 5 | import org.junit.runner.RunWith; 6 | 7 | /** 8 | * @author @aloyer 9 | */ 10 | @RunWith(Cucumber.class) 11 | @CucumberOptions( 12 | format = "tzatziki.analysis.exec.gson.JsonEmitterReport:target/report/all" 13 | ) 14 | public class RunAllCucumberTest { 15 | } 16 | -------------------------------------------------------------------------------- /tzatziki-core/src/test/resources/tzatziki/analysis/exec/gson/steps-w-embedding.feature: -------------------------------------------------------------------------------- 1 | Feature: Embedding at the step level 2 | 3 | Scenario: Step should be able to emit embedding 4 | 5 | Given a series of 10 values named 'xs' 6 | When I apply the following formula: 7 | """ 8 | result = xs.reduce(0, (x, sum) -> sum + x) / xs.size() 9 | """ 10 | Then the result should be greater or equal to the lowest value of 'xs' 11 | And the result should be lower or equal to the highest value of 'xs' -------------------------------------------------------------------------------- /tzatziki-pdf/src/main/java/tzatziki/pdf/model/Steps.java: -------------------------------------------------------------------------------- 1 | package tzatziki.pdf.model; 2 | 3 | import tzatziki.analysis.exec.model.StepExec; 4 | 5 | /** 6 | * @author @aloyer 7 | */ 8 | public class Steps { 9 | private final Iterable steps; 10 | 11 | public Steps(Iterable steps) { 12 | this.steps = steps; 13 | } 14 | 15 | public Iterable steps() { 16 | return steps; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tzatziki-pdf/src/test/resources/tzatziki/pdf/tags.properties: -------------------------------------------------------------------------------- 1 | wip=Work in progress 2 | 3 | takeOrder=Take order 4 | protocol=Command serialization 5 | payment=Payment 6 | 7 | notEnoughMoney=Not enough money 8 | tooMuchMoney=Too much money 9 | 10 | 11 | coffee=Coffee 12 | tea=Tea 13 | chocolate=Chocolate 14 | orangeJuice=Orange Juice 15 | 16 | sugar=Sugar 17 | noSugar=No sugar 18 | extraHot=Xtra Hot 19 | 20 | message=Message 21 | reporting=Reporting 22 | 23 | notification=Notification 24 | runningOut=Running out some drink 25 | 26 | 27 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/exec/model/Status.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec.model; 2 | 3 | /** 4 | * @author @aloyer 5 | */ 6 | public enum Status { 7 | Passed, 8 | Skipped, 9 | Undefined, 10 | Failed, 11 | Pending; 12 | 13 | public static Status fromString(String status) { 14 | for (Status s : values()) { 15 | if(s.name().equalsIgnoreCase(status)) 16 | return s; 17 | } 18 | return null; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /tzatziki-core/src/test/resources/tzatziki/analysis/exec/gson/comments-n-description-interleaved.feature: -------------------------------------------------------------------------------- 1 | Feature: Comments and description interleaved 2 | 3 | # Feature description (within comments) 4 | 5 | Scenario: First Scenario 6 | 7 | # This is the first scenario 8 | 9 | Given a series of 10 values named 'xs' 10 | 11 | # This is the end of the first scenario 12 | 13 | Scenario: Second Scenario 14 | 15 | # This is the second scenario 16 | 17 | Given a series of 10 values named 'xs' 18 | 19 | # This is the end of the second scenario 20 | 21 | -------------------------------------------------------------------------------- /tzatziki-samples/src/test/java/samples/coffeemachine/CoffeeMachineFeatureTest.java: -------------------------------------------------------------------------------- 1 | package samples.coffeemachine; 2 | 3 | import cucumber.api.CucumberOptions; 4 | import cucumber.api.junit.Cucumber; 5 | import org.junit.AfterClass; 6 | import org.junit.runner.RunWith; 7 | 8 | /** 9 | * @author @aloyer 10 | */ 11 | @RunWith(Cucumber.class) 12 | @CucumberOptions( 13 | format = "tzatziki.analysis.exec.gson.JsonEmitterReport:target/samples/coffeemachine" 14 | ) 15 | public class CoffeeMachineFeatureTest { 16 | } 17 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/exec/model/LineRange.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec.model; 2 | 3 | /** 4 | * @author @aloyer 5 | */ 6 | public class LineRange { 7 | public final Integer first; 8 | public final Integer last; 9 | 10 | public LineRange(Integer first, Integer last) { 11 | this.first = first; 12 | this.last = last; 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | return "LineRange[" + first + ", " + last + ']'; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tzatziki-samples/src/test/java/samples/coffeemachine/CoffeeMachineWipFeatureTest.java: -------------------------------------------------------------------------------- 1 | package samples.coffeemachine; 2 | 3 | import cucumber.api.CucumberOptions; 4 | import cucumber.api.junit.Cucumber; 5 | import org.junit.runner.RunWith; 6 | 7 | /** 8 | * @author @aloyer 9 | */ 10 | @RunWith(Cucumber.class) 11 | @CucumberOptions( 12 | tags = {"@wip"}, 13 | format = "tzatziki.analysis.exec.gson.JsonEmitterReport:target/samples/coffeemachine/wip" 14 | ) 15 | public class CoffeeMachineWipFeatureTest { 16 | } 17 | -------------------------------------------------------------------------------- /tzatziki-core/src/test/resources/tzatziki/analysis/exec/feature/feature-w-outline.feature: -------------------------------------------------------------------------------- 1 | Feature: A feature with an Outline 2 | 3 | @wip 4 | Scenario Outline: Template scenario 5 | 6 | Given a simple step 7 | When I activate the 8 | Then I should have a log that indicate the activation of the 9 | 10 | @activated 11 | Examples: Activated Features 12 | | behavior | 13 | | print | 14 | | login | 15 | 16 | @deactivated 17 | Examples: Deactivated Features 18 | | behavior | 19 | | delete | 20 | | clone | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/step/FeatureVisitor.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.step; 2 | 3 | /** 4 | * @author @aloyer 5 | */ 6 | public interface FeatureVisitor { 7 | void enterFeature(Feature feature); 8 | void exitFeature(Feature feature); 9 | 10 | void enterScenario(Scenario scenario); 11 | void exitScenario(Scenario scenario); 12 | 13 | void enterScenarioOutline(ScenarioOutline scenario); 14 | void exitScenarioOutline(ScenarioOutline scenario); 15 | 16 | void visitStep(Step step); 17 | } 18 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/coffeemachine/tags.properties: -------------------------------------------------------------------------------- 1 | wip=Work in progress 2 | wip1= 3 | wip2= 4 | 5 | unusedTag= 6 | 7 | takeOrder=Take order 8 | protocol=Command serialization 9 | payment=Payment 10 | 11 | notEnoughMoney=Not enough money 12 | tooMuchMoney=Too much money 13 | 14 | 15 | coffee=Coffee 16 | tea=Tea 17 | chocolate=Chocolate 18 | orangeJuice=Orange Juice 19 | 20 | sugar=Sugar 21 | noSugar=No sugar 22 | extraHot=Xtra Hot 23 | 24 | message=Message 25 | reporting=Reporting 26 | 27 | notification=Notification 28 | runningOut=Running out some drink 29 | 30 | 31 | -------------------------------------------------------------------------------- /tzatziki-pdf/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tzatziki-core/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/java/KeywordBasedPattern.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.java; 2 | 3 | /** 4 | * @author @aloyer 5 | */ 6 | public class KeywordBasedPattern { 7 | private final String keyword; 8 | private final String pattern; 9 | 10 | public KeywordBasedPattern(String keyword, String pattern) { 11 | this.keyword = keyword; 12 | this.pattern = pattern; 13 | } 14 | 15 | public String getKeyword() { 16 | return keyword; 17 | } 18 | 19 | public String getPattern() { 20 | return pattern; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/util/ExceptionUtils.java: -------------------------------------------------------------------------------- 1 | package tzatziki.util; 2 | 3 | import java.io.PrintWriter; 4 | import java.io.StringWriter; 5 | 6 | /** 7 | * @author @aloyer 8 | */ 9 | public class ExceptionUtils { 10 | public static String toString(Throwable error) { 11 | if (error == null) 12 | return null; 13 | 14 | StringWriter stringWriter = new StringWriter(); 15 | PrintWriter printWriter = new PrintWriter(stringWriter); 16 | error.printStackTrace(printWriter); 17 | return stringWriter.toString(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/stylius/frontend/user_login_via_oauth.feature: -------------------------------------------------------------------------------- 1 | @users @oauth 2 | Feature: Sign in to the store via OAuth 3 | In order to view my orders list 4 | As a visitor with an OAuth account 5 | I need to be able to log in to the store 6 | 7 | Background: 8 | Given I am not logged in 9 | And I am on the store homepage 10 | 11 | Scenario Outline: Get to the OAuth login page 12 | When I follow "Login" 13 | Then I should see the connect with "" button 14 | 15 | Examples: 16 | | oauth | 17 | | Amazon | 18 | | Facebook | 19 | | Google | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/java/GrammarVisitor.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.java; 2 | 3 | /** 4 | * @author @aloyer 5 | */ 6 | public class GrammarVisitor { 7 | public void enter(PackageEntry packageEntry, ClassEntry classEntry) { 8 | } 9 | 10 | public void leave(PackageEntry packageEntry, ClassEntry classEntry) { 11 | } 12 | 13 | public void visit(PackageEntry packageEntry, ClassEntry classEntry, MethodEntry methodEntry) { 14 | } 15 | 16 | public void enter(PackageEntry packageEntry) { 17 | } 18 | 19 | public void leave(PackageEntry packageEntry) { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/tag/Tag.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.tag; 2 | 3 | 4 | /** 5 | * @author @aloyer 6 | */ 7 | public class Tag { 8 | private final String tag; 9 | private String description; 10 | 11 | public Tag(String tag) { 12 | this.tag = tag; 13 | } 14 | 15 | public String getTag() { 16 | return tag; 17 | } 18 | 19 | 20 | public Tag declareDescription(String description) { 21 | this.description = description; 22 | return this; 23 | } 24 | 25 | public String getDescription() { 26 | return description; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/sample/game-of-life.feature: -------------------------------------------------------------------------------- 1 | @noui 2 | Feature: The game of life should... 3 | 1. Any live cell with fewer than two live neighbours dies, as if caused by underpopulation. 4 | 2. Any live cell with more than three live neighbours dies, as if by overcrowding. 5 | 3. Any live cell with two or three live neighbours lives on to the next generation. 6 | 4. Any dead cell with exactly three live neighbours becomes a live cell. 7 | 8 | @underpopulation 9 | Scenario: Cell has no neighbors 10 | 11 | Given Cell is alive 12 | And Cell has "0" neighbors 13 | When I go to the next generation 14 | Then Cell should be dead 15 | -------------------------------------------------------------------------------- /tzatziki-samples/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tzatziki-pdf/src/main/java/tzatziki/pdf/Comments.java: -------------------------------------------------------------------------------- 1 | package tzatziki.pdf; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | /** 6 | * @author @aloyer 7 | */ 8 | public class Comments { 9 | public static final String NL = "\n"; 10 | private static final String COMMENT = "#"; 11 | private static final Pattern COMMENT_PATTERN = Pattern.compile("^\\s*" + COMMENT); 12 | 13 | public static String discardCommentChar(String value) { 14 | return COMMENT_PATTERN.matcher(value).replaceAll(""); 15 | } 16 | 17 | public static boolean startsWithComment(String text) { 18 | return text.startsWith(COMMENT); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/java/samples/coffeemachine/Context.java: -------------------------------------------------------------------------------- 1 | package samples.coffeemachine; 2 | 3 | import org.mockito.Mockito; 4 | 5 | /** 6 | * @author @aloyer 7 | */ 8 | public class Context { 9 | 10 | private DrinkMaker drinkMaker; 11 | private Gateway gateway; 12 | 13 | public Context() { 14 | drinkMaker = Mockito.mock(DrinkMaker.class); 15 | } 16 | 17 | public DrinkMaker getDrinkMaker() { 18 | return drinkMaker; 19 | } 20 | 21 | public Gateway getGateway() { 22 | if (gateway == null) 23 | gateway = new Gateway(drinkMaker); 24 | return gateway; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /tzatziki-web/src/main/java/tzatziki/web/AppConfiguration.java: -------------------------------------------------------------------------------- 1 | package tzatziki.web; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import io.dropwizard.Configuration; 5 | import io.dropwizard.db.DataSourceFactory; 6 | 7 | import javax.validation.Valid; 8 | import javax.validation.constraints.NotNull; 9 | 10 | /** 11 | * @author @aloyer 12 | */ 13 | public class AppConfiguration extends Configuration { 14 | @Valid 15 | @NotNull 16 | @JsonProperty 17 | private DataSourceFactory database = new DataSourceFactory(); 18 | 19 | public DataSourceFactory getDataSourceFactory() { 20 | return database; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tzatziki-web/src/main/java/tzatziki/web/GrammarDAOHealthCheck.java: -------------------------------------------------------------------------------- 1 | package tzatziki.web; 2 | 3 | import com.codahale.metrics.health.HealthCheck; 4 | 5 | /** 6 | * @author @aloyer 7 | */ 8 | public class GrammarDAOHealthCheck extends HealthCheck { 9 | private final GrammarDAO grammarDAO; 10 | 11 | public GrammarDAOHealthCheck(GrammarDAO grammarDAO) { 12 | this.grammarDAO = grammarDAO; 13 | } 14 | 15 | @Override 16 | protected Result check() throws Exception { 17 | if (grammarDAO.getGrammar() == null) 18 | return Result.unhealthy("No grammar available"); 19 | return Result.healthy(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/stylius/frontend/account_homepage.feature: -------------------------------------------------------------------------------- 1 | @account 2 | Feature: User account homepage 3 | In order to access and manage my personal information 4 | As a logged user 5 | I want to be able to see my account homepage 6 | 7 | Scenario: Displaying the my account section only to logged users 8 | Given I am on the store homepage 9 | Then I should not see "My account" 10 | 11 | Scenario: Viewing the homepage of my account 12 | Given I am on the store homepage 13 | And I am logged in user 14 | When I follow "My account" 15 | Then I should be on my account homepage 16 | And I should see "Welcome to your space" 17 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/java/Parameter.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.java; 2 | 3 | /** 4 | * @author @aloyer 5 | */ 6 | public class Parameter { 7 | private final int index; 8 | private final String name; 9 | private final String parameterDoc; 10 | 11 | public Parameter(int index, String name, String parameterDoc) { 12 | this.index = index; 13 | this.name = name; 14 | this.parameterDoc = parameterDoc; 15 | } 16 | 17 | public int getIndex() { 18 | return index; 19 | } 20 | 21 | public String getName() { 22 | return name; 23 | } 24 | 25 | public String getDoc() { 26 | return parameterDoc; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tzatziki-web/src/main/java/tzatziki/web/ScenarioDAOHealthCheck.java: -------------------------------------------------------------------------------- 1 | package tzatziki.web; 2 | 3 | import com.codahale.metrics.health.HealthCheck; 4 | 5 | /** 6 | * @author @aloyer 7 | */ 8 | public class ScenarioDAOHealthCheck extends HealthCheck { 9 | private final ScenarioDAO scenarioDAO; 10 | 11 | public ScenarioDAOHealthCheck(ScenarioDAO scenarioDAO) { 12 | this.scenarioDAO = scenarioDAO; 13 | } 14 | 15 | 16 | @Override 17 | protected Result check() throws Exception { 18 | try { 19 | scenarioDAO.check(); 20 | return Result.healthy(); 21 | } catch (Exception e) { 22 | return Result.unhealthy(e.getMessage()); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tzatziki-web/conf/config.yml: -------------------------------------------------------------------------------- 1 | # Database settings. 2 | database: 3 | 4 | # the name of your JDBC driver 5 | driverClass: org.h2.Driver 6 | 7 | # the username 8 | user: sa 9 | 10 | # the password 11 | password: sa 12 | 13 | # the JDBC URL 14 | url: jdbc:h2:./target/example 15 | 16 | server: 17 | type: simple 18 | applicationContextPath: /api/* # Default value* 19 | 20 | # Logging settings. 21 | logging: 22 | 23 | # The default level of all loggers. Can be OFF, ERROR, WARN, INFO, DEBUG, TRACE, or ALL. 24 | level: INFO 25 | 26 | # Logger-specific levels. 27 | loggers: 28 | 29 | # Sets the level for 'com.example.app' to DEBUG. 30 | com.example.app: DEBUG 31 | 32 | org.hibernate.SQL: ALL 33 | 34 | appenders: 35 | - type: console -------------------------------------------------------------------------------- /tzatziki-web/src/main/java/tzatziki/web/GrammarResource.java: -------------------------------------------------------------------------------- 1 | package tzatziki.web; 2 | 3 | import com.codahale.metrics.annotation.Timed; 4 | import tzatziki.analysis.java.Grammar; 5 | 6 | import javax.ws.rs.GET; 7 | import javax.ws.rs.Path; 8 | import javax.ws.rs.Produces; 9 | import javax.ws.rs.core.MediaType; 10 | 11 | /** 12 | * @author @aloyer 13 | */ 14 | @Path("/grammar") 15 | @Produces(MediaType.APPLICATION_JSON) 16 | public class GrammarResource { 17 | 18 | private final GrammarDAO grammarDAO; 19 | 20 | public GrammarResource(GrammarDAO grammarDAO) { 21 | this.grammarDAO = grammarDAO; 22 | } 23 | 24 | @GET 25 | @Timed 26 | public Grammar grammar() { 27 | return grammarDAO.getGrammar(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tzatziki-core/src/test/java/tzatziki/analysis/exec/tag/TagFilterTest.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec.tag; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | 7 | public class TagFilterTest { 8 | 9 | 10 | @Test 11 | public void simple_tag_filtering() { 12 | TagFilter tagFilter = TagFilter.from("~@wip", "~@default", "@specs"); 13 | 14 | assertThat(tagFilter.apply(Tags.from("@wip"))).isFalse(); 15 | assertThat(tagFilter.apply(Tags.from("@wip", "@option"))).isFalse(); 16 | assertThat(tagFilter.apply(Tags.from("@defaults"))).isFalse(); 17 | assertThat(tagFilter.apply(Tags.from("@defaults", "@option"))).isFalse(); 18 | assertThat(tagFilter.apply(Tags.from("@defaults", "@specs"))).isTrue(); 19 | } 20 | } -------------------------------------------------------------------------------- /tzatziki-core/src/test/java/tzatziki/util/ExceptionUtilsTest.java: -------------------------------------------------------------------------------- 1 | package tzatziki.util; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | 7 | public class ExceptionUtilsTest { 8 | 9 | @Test 10 | public void toString_should_return_null__when_null_is_provided() { 11 | assertThat(ExceptionUtils.toString(null)).isNull(); 12 | } 13 | 14 | @Test 15 | public void toString_should_return_the_stacktrace() { 16 | Exception ex = new Exception("Erf"); 17 | String str = ExceptionUtils.toString(ex); 18 | assertThat(str).startsWith("" + 19 | "java.lang.Exception: Erf\n" + 20 | "\tat tzatziki.util.ExceptionUtilsTest.toString_should_return_the_stacktrace(ExceptionUtilsTest.java:"); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/exec/model/BackgroundExec.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec.model; 2 | 3 | /** 4 | * @author @aloyer 5 | */ 6 | public class BackgroundExec extends StepContainer { 7 | 8 | private final String keyword; 9 | private final String name; 10 | 11 | public BackgroundExec(String keyword, String name) { 12 | this.keyword = keyword; 13 | this.name = name; 14 | } 15 | 16 | public String keyword() { 17 | return keyword; 18 | } 19 | 20 | public String name() { 21 | return name; 22 | } 23 | 24 | public BackgroundExec recursiveCopy() { 25 | BackgroundExec copy = new BackgroundExec(keyword, name); 26 | recursiveCopy(copy); 27 | return copy; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/check/CheckAtLeastOneTagsExist.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.check; 2 | 3 | import java.util.List; 4 | 5 | import org.junit.Assert; 6 | 7 | import tzatziki.analysis.tag.TagDictionary; 8 | 9 | /** 10 | * Check that at least on declared tag exist in the TagDictionary. 11 | * It can be used to check that there is no orphan test regarding a categorization. 12 | * 13 | * @author pverdage 14 | * 15 | */ 16 | public class CheckAtLeastOneTagsExist implements TagChecker { 17 | 18 | @Override 19 | public void evaluate(TagDictionary dictionary, List tags) { 20 | for (String tag : tags) { 21 | if (dictionary.containsTag(tag)) 22 | return; 23 | } 24 | Assert.fail("No tag(s) in dictionary amongst: " + tags); 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /tzatziki-core/src/test/java/tzatziki/analysis/java/MethodEntryTest.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.java; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.Arrays; 6 | 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | 9 | public class MethodEntryTest { 10 | 11 | @Test 12 | public void should_return_patterns() { 13 | MethodEntry entry = new MethodEntry("bookADeal", Arrays.asList("java.lang.String")); 14 | entry.declarePattern("Given", "^a standard deal (.*) with no specifics"); 15 | entry.declarePattern("Given", "^I have booked a standard deal (.*) with no specifics"); 16 | 17 | assertThat(entry.patterns().toList()).containsExactly( 18 | "^a standard deal (.*) with no specifics", 19 | "^I have booked a standard deal (.*) with no specifics"); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/check/CheckAllTagsExist.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.check; 2 | 3 | import java.util.List; 4 | 5 | import org.junit.Assert; 6 | 7 | import com.google.common.collect.Lists; 8 | 9 | import tzatziki.analysis.tag.TagDictionary; 10 | 11 | /** 12 | * Check that all tags are declared in the TagDictionary 13 | * @author pverdage 14 | * 15 | */ 16 | public class CheckAllTagsExist implements TagChecker { 17 | 18 | @Override 19 | public void evaluate(TagDictionary dictionary, List tags) { 20 | List unknown = Lists.newArrayList(); 21 | for (String tag : tags) { 22 | if (!dictionary.containsTag(tag)) 23 | unknown.add(tag); 24 | } 25 | if (!unknown.isEmpty()) 26 | Assert.fail("Unknown tag(s): " + unknown); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/sample/countries.feature: -------------------------------------------------------------------------------- 1 | @addressing @select 2 | Feature: Countries and provinces 3 | In order to create tax and shipping zones 4 | As a store owner 5 | I want to be able to manage countries and their provinces 6 | 7 | Background: 8 | Given I am logged in as administrator 9 | And there are following countries: 10 | | name | provinces | 11 | | France | Lyon, Toulouse, Rennes, Nancy | 12 | | China | | 13 | | Ukraine | Kiev, Odessa, Cherkasy, Kharkiv | 14 | 15 | Scenario: Seeing index of all countries 16 | Given I am on the dashboard page 17 | When I follow "Countries" 18 | Then I should be on the country index page 19 | And I should see 3 countries in the list 20 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/java/GrammarParserListener.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.java; 2 | 3 | import com.thoughtworks.qdox.model.JavaClass; 4 | import com.thoughtworks.qdox.model.JavaMethod; 5 | import com.thoughtworks.qdox.model.JavaPackage; 6 | 7 | import java.util.Collection; 8 | 9 | /** 10 | * @author @aloyer 11 | */ 12 | public interface GrammarParserListener { 13 | void aboutToParsePackages(Collection packages); 14 | 15 | void aboutToParseClasses(Collection classes); 16 | 17 | void enteringPackage(JavaPackage pkg); 18 | 19 | void exitingPackage(JavaPackage pkg); 20 | 21 | void enteringClass(JavaClass klazz); 22 | 23 | void exitingClass(JavaClass klazz); 24 | 25 | void enteringMethod(JavaMethod method); 26 | 27 | void exitingMethod(JavaMethod method); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/step/FeatureVisitorAdapter.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.step; 2 | 3 | /** 4 | * @author @aloyer 5 | */ 6 | public class FeatureVisitorAdapter implements FeatureVisitor { 7 | 8 | @Override 9 | public void enterFeature(Feature feature) { 10 | } 11 | 12 | @Override 13 | public void exitFeature(Feature feature) { 14 | } 15 | 16 | @Override 17 | public void enterScenario(Scenario scenario) { 18 | } 19 | 20 | @Override 21 | public void exitScenario(Scenario scenario) { 22 | } 23 | 24 | @Override 25 | public void enterScenarioOutline(ScenarioOutline scenario) { 26 | } 27 | 28 | @Override 29 | public void exitScenarioOutline(ScenarioOutline scenario) { 30 | } 31 | 32 | @Override 33 | public void visitStep(Step step) { 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/exec/model/ExamplesRow.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec.model; 2 | 3 | import com.google.common.collect.FluentIterable; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author @aloyer 9 | */ 10 | public class ExamplesRow { 11 | private final List comments; 12 | private final List cells; 13 | private final Integer line; 14 | 15 | public ExamplesRow(List comments, List cells, Integer line) { 16 | this.comments = comments; 17 | this.cells = cells; 18 | this.line = line; 19 | } 20 | 21 | public FluentIterable comments() { 22 | return FluentIterable.from(comments); 23 | } 24 | 25 | public FluentIterable cells() { 26 | return FluentIterable.from(cells); 27 | } 28 | 29 | public Integer getLine() { 30 | return line; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/exec/model/Embedded.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec.model; 2 | 3 | /** 4 | * @author @aloyer 5 | */ 6 | public class Embedded { 7 | private final String mimeType; 8 | private final byte[] data; 9 | private final String text; 10 | 11 | public Embedded(String mimeType, byte[] data) { 12 | this.mimeType = mimeType; 13 | this.data = data; 14 | this.text = null; 15 | } 16 | 17 | public Embedded(String text) { 18 | this.mimeType = "plain/text"; 19 | this.data = null; 20 | this.text = text; 21 | } 22 | 23 | public boolean isText() { 24 | return text != null; 25 | } 26 | 27 | public String mimeType() { 28 | return mimeType; 29 | } 30 | 31 | public String text() { 32 | return text; 33 | } 34 | 35 | public byte[] data() { 36 | return data; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/exec/model/EmbeddingAndWriteContainer.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec.model; 2 | 3 | import com.google.common.collect.FluentIterable; 4 | import com.google.common.collect.Lists; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author @aloyer 10 | */ 11 | public class EmbeddingAndWriteContainer { 12 | private List embeddedList = Lists.newArrayList(); 13 | 14 | public void embedding(String mimeType, byte[] data) { 15 | embeddedList.add(new Embedded(mimeType, data)); 16 | } 17 | 18 | public void text(String text) { 19 | embeddedList.add(new Embedded(text)); 20 | } 21 | 22 | protected void recursiveCopy(EmbeddingAndWriteContainer copy) { 23 | copy.embeddedList.addAll(embeddedList); 24 | } 25 | 26 | public FluentIterable embeddeds() { 27 | return FluentIterable.from(embeddedList); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/step/Features.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.step; 2 | 3 | import com.google.common.collect.FluentIterable; 4 | import com.google.common.collect.Lists; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author @aloyer 10 | */ 11 | public class Features { 12 | private final List featureList = Lists.newArrayList(); 13 | 14 | public Features() { 15 | } 16 | 17 | public void add(Feature feature) { 18 | featureList.add(feature); 19 | } 20 | 21 | public void traverse(FeatureVisitor visitor) { 22 | for (Feature feature : featureList) { 23 | feature.traverse(visitor); 24 | } 25 | } 26 | 27 | public FluentIterable features() { 28 | return FluentIterable.from(featureList); 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return "Features{" + featureList + '}'; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tzatziki-pdf/src/test/resources/tzatziki/pdf/feature/01-sample.feature: -------------------------------------------------------------------------------- 1 | Feature: A really basic feature 2 | 3 | Scenario Outline: A template scenario 4 | 5 | Given a element of 6 | When I shake it 7 | Then it should be 8 | 9 | Examples: Thinks 10 | | type | shaked-or-not | 11 | | car | shaked | 12 | | building | not shaked | 13 | 14 | Examples: Animals 15 | | type | shaked-or-not | 16 | | ant | shaked | 17 | | bee | shaked | 18 | 19 | 20 | Scenario Outline: An other template scenario 21 | 22 | Given a element of 23 | When I shake it 24 | Then it should be 25 | 26 | Examples: Thinks 27 | | type | shaked-or-not | 28 | | car | shaked | 29 | | building | not shaked | 30 | 31 | Examples: Animals 32 | | type | shaked-or-not | 33 | | ant | shaked | 34 | | bee | shaked | -------------------------------------------------------------------------------- /tzatziki-pdf/src/main/java/tzatziki/pdf/Margin.java: -------------------------------------------------------------------------------- 1 | package tzatziki.pdf; 2 | 3 | /** 4 | * @author @aloyer 5 | */ 6 | public class Margin { 7 | 8 | public static Margin create(float margin) { 9 | return new Margin(margin); 10 | } 11 | 12 | public final float marginLeft; 13 | public final float marginRight; 14 | public final float marginTop; 15 | public final float marginBottom; 16 | 17 | public Margin(float margin) { 18 | this(margin, margin, margin, margin); 19 | } 20 | 21 | public Margin(float marginLeftRight, float marginTopBottom) { 22 | this(marginLeftRight, marginLeftRight, marginTopBottom, marginTopBottom); 23 | } 24 | 25 | public Margin(float marginLeft, float marginRight, float marginTop, float marginBottom) { 26 | this.marginLeft = marginLeft; 27 | this.marginRight = marginRight; 28 | this.marginTop = marginTop; 29 | this.marginBottom = marginBottom; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/step/Step.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.step; 2 | 3 | /** 4 | * @author @aloyer 5 | */ 6 | public class Step { 7 | private final String keyword; 8 | private final String text; 9 | private int grammarMatchCount; 10 | 11 | public Step(String keyword, String text) { 12 | this.keyword = keyword.trim(); 13 | this.text = text; 14 | } 15 | 16 | public String getKeyword() { 17 | return keyword; 18 | } 19 | 20 | public String getText() { 21 | return text; 22 | } 23 | 24 | public void traverse(FeatureVisitor visitor) { 25 | visitor.visitStep(this); 26 | } 27 | 28 | public void grammarMatchCount(int nb) { 29 | grammarMatchCount = nb; 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return "Step{" + 35 | "@" + keyword + '(' + text + ')' + 36 | ", matchCount=" + grammarMatchCount + 37 | '}'; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tzatziki-core/src/test/resources/tzatziki/analysis/step/subdomain/extra-hot.feature: -------------------------------------------------------------------------------- 1 | Feature: Extra Hot and Orange Juice 2 | 3 | # **In order to** provide more choice and to attract more customer 4 | # 5 | # **As a** shopkeeper 6 | # 7 | # **I want to** be able to make orange juice and to deliver extra hot drinks 8 | 9 | @ProtocolOrder 10 | Scenario: An extra hot tea with 1 sugar 11 | 12 | When I order an extra hot "Tea" with 1 sugar 13 | Then the instruction generated should be "Th:1:0" 14 | 15 | @ProtocolOrder 16 | Scenario: An Orange juice 17 | 18 | When I order an "Orange Juice" 19 | Then the instruction generated should be "O::" 20 | 21 | @ProtocolOrder 22 | @wip 23 | Scenario: Extra sugar with Orange Juice is ignored 24 | 25 | When I order an "Orange Juice" with 1 sugar 26 | Then the instruction generated should be "O::" 27 | 28 | @ProtocolOrder 29 | Scenario: Extra hot with Orange Juice is ignored 30 | 31 | When I order an extra hot "Orange Juice" with 1 sugar 32 | Then the instruction generated should be "O::" 33 | -------------------------------------------------------------------------------- /release.md: -------------------------------------------------------------------------------- 1 | Release 2 | ------- 3 | 4 | ## Jdk6 Compilation 5 | 6 | ``` 7 | $ docker pull jamesdbloom/docker-java6-maven 8 | $ docker run --rm -v $(pwd)/:/wk -w /wk -it jamesdbloom/docker-java6-maven /bin/bash 9 | [ root@4291b084fd0c:/local/git ]$ export LC_ALL="C.UTF-8" 10 | [ root@4291b084fd0c:/local/git ]$ export LANG="$LC_ALL" 11 | [ root@4291b084fd0c:/local/git ]$ git clone https://github.com/Arnauld/tzatziki.git 12 | [ root@4291b084fd0c:/local/git ]$ cd tzatziki && mvn clean test 13 | ``` 14 | 15 | ## Release 16 | 17 | First check for **snapshot** dependencies: 18 | 19 | ```bash 20 | fgrep -i SNAPSHOT **/pom.xml 21 | ``` 22 | 23 | [Maven Release Plugin: The Final Nail in the Coffin](http://axelfontaine.com/blog/final-nail.html) 24 | 25 | ```bash 26 | mvn versions:set -DnewVersion=1.0.1 27 | mvn clean deploy scm:tag -Psign-artifacts 28 | git status 29 | git add . 30 | git commit -m "gutenberg 1.0.1" 31 | mvn versions:set -DnewVersion=1.0.2-SNAPSHOT 32 | git add . 33 | git commit -m "gutenberg 1.0.2-snapshot" 34 | git push 35 | ``` 36 | -------------------------------------------------------------------------------- /tzatziki-pdf/src/main/java/tzatziki/pdf/model/ScenarioOutlineWithResolved.java: -------------------------------------------------------------------------------- 1 | package tzatziki.pdf.model; 2 | 3 | import com.google.common.collect.FluentIterable; 4 | import tzatziki.analysis.exec.model.ScenarioExec; 5 | import tzatziki.analysis.exec.model.ScenarioOutlineExec; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * @author @aloyer 12 | */ 13 | public class ScenarioOutlineWithResolved { 14 | private final ScenarioOutlineExec outlineExec; 15 | private final List resolvedExec = new ArrayList(); 16 | 17 | public ScenarioOutlineWithResolved(ScenarioOutlineExec outlineExec) { 18 | this.outlineExec = outlineExec; 19 | } 20 | 21 | public void declareScenario(ScenarioExec scenario) { 22 | resolvedExec.add(scenario); 23 | } 24 | 25 | public ScenarioOutlineExec outline() { 26 | return outlineExec; 27 | } 28 | 29 | public FluentIterable resolved() { 30 | return FluentIterable.from(resolvedExec); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/exec/tag/TagExpressionPredicate.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec.tag; 2 | 3 | import com.google.common.base.Predicate; 4 | import com.google.common.collect.Lists; 5 | import gherkin.TagExpression; 6 | import gherkin.formatter.model.Tag; 7 | 8 | import java.util.Collection; 9 | import java.util.List; 10 | 11 | /** 12 | * @author @aloyer 13 | */ 14 | public class TagExpressionPredicate implements Predicate { 15 | private final TagExpression tagExpression; 16 | 17 | public TagExpressionPredicate(List tagExprs) { 18 | this.tagExpression = new TagExpression(tagExprs); 19 | } 20 | 21 | @Override 22 | public boolean apply(Tags input) { 23 | return tagExpression.evaluate(toGherkinTags(input.toList())); 24 | } 25 | 26 | private static Collection toGherkinTags(List strings) { 27 | List tags = Lists.newArrayList(); 28 | for (String str : strings) { 29 | tags.add(new Tag(str, -1)); 30 | } 31 | return tags; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/exec/tag/Tags.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec.tag; 2 | 3 | import com.google.common.collect.Sets; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.Collection; 8 | import java.util.List; 9 | import java.util.Set; 10 | 11 | /** 12 | * @author @aloyer 13 | */ 14 | public class Tags { 15 | 16 | public static Tags from(String... tags) { 17 | return new Tags(Arrays.asList(tags)); 18 | } 19 | 20 | public static Tags from(Collection tags) { 21 | return new Tags(tags); 22 | } 23 | 24 | private final Collection tags; 25 | 26 | public Tags(Collection tags) { 27 | this.tags = tags; 28 | } 29 | 30 | 31 | public List toList() { 32 | return new ArrayList(tags); 33 | } 34 | 35 | public Tags completeWith(Collection tags) { 36 | Set merged = Sets.newHashSet(); 37 | merged.addAll(this.tags); 38 | merged.addAll(tags); 39 | return Tags.from(merged); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Arnauld Loyer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/step/Scenario.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.step; 2 | 3 | import com.google.common.collect.Lists; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author @aloyer 9 | */ 10 | public class Scenario { 11 | private String visualName; 12 | private List tags = Lists.newArrayList(); 13 | private List stepList = Lists.newArrayList(); 14 | 15 | public void setVisualName(String visualName) { 16 | this.visualName = visualName; 17 | } 18 | 19 | public String getVisualName() { 20 | return visualName; 21 | } 22 | 23 | public void add(Step step) { 24 | stepList.add(step); 25 | } 26 | 27 | public void traverse(FeatureVisitor visitor) { 28 | visitor.enterScenario(this); 29 | for (Step step : stepList) 30 | step.traverse(visitor); 31 | visitor.exitScenario(this); 32 | } 33 | 34 | public void addTags(List tags) { 35 | this.tags.addAll(tags); 36 | } 37 | 38 | public List getTags() { 39 | return tags; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/exec/gson/ScenarioExecSerializer.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec.gson; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonObject; 6 | import com.google.gson.JsonSerializationContext; 7 | import com.google.gson.JsonSerializer; 8 | 9 | import java.lang.reflect.Type; 10 | 11 | import static tzatziki.analysis.exec.gson.StepContainerDeserializer.SCENARIO; 12 | import static tzatziki.analysis.exec.gson.StepContainerDeserializer.TYPE; 13 | 14 | /** 15 | * @author @aloyer 16 | */ 17 | public class ScenarioExecSerializer implements JsonSerializer { 18 | 19 | private final Gson delegate; 20 | 21 | public ScenarioExecSerializer(Gson delegate) { 22 | this.delegate = delegate; 23 | } 24 | 25 | @Override 26 | public JsonElement serialize(Object src, Type typeOfSrc, JsonSerializationContext context) { 27 | JsonObject serialized = delegate.toJsonTree(src, typeOfSrc).getAsJsonObject(); 28 | serialized.addProperty(TYPE, SCENARIO); 29 | return serialized; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/coffeemachine/03-extra-hot.feature: -------------------------------------------------------------------------------- 1 | Feature: Extra Hot and Orange Juice 2 | 3 | # **In order to** provide more choice and to attract more customer 4 | # 5 | # **As a** shopkeeper 6 | # 7 | # **I want to** be able to make orange juice and to deliver extra hot drinks 8 | 9 | @takeOrder @tea @sugar @extraHot @protocol 10 | Scenario: An extra hot tea with 1 sugar 11 | 12 | When I order an extra hot "Tea" with 1 sugar 13 | Then the instruction generated should be "Th:1:0" 14 | 15 | @takeOrder @orangeJuice @protocol 16 | Scenario: An Orange juice 17 | 18 | When I order an "Orange Juice" 19 | Then the instruction generated should be "O::" 20 | 21 | @takeOrder @orangeJuice @sugar @protocol 22 | @wip 23 | Scenario: Extra sugar with Orange Juice is ignored 24 | 25 | When I order an "Orange Juice" with 1 sugar 26 | Then the instruction generated should be "O::" 27 | 28 | @takeOrder @orangeJuice @sugar @extraHot @protocol 29 | Scenario: Extra hot with Orange Juice is ignored 30 | 31 | When I order an extra hot "Orange Juice" with 1 sugar 32 | Then the instruction generated should be "O::" 33 | -------------------------------------------------------------------------------- /tzatziki-core/src/test/resources/tzatziki/junit/coffeemachine/03-extra-hot.feature: -------------------------------------------------------------------------------- 1 | Feature: Extra Hot and Orange Juice 2 | 3 | # **In order to** provide more choice and to attract more customer 4 | # 5 | # **As a** shopkeeper 6 | # 7 | # **I want to** be able to make orange juice and to deliver extra hot drinks 8 | 9 | @takeOrder @tea @sugar @extraHot @protocol 10 | Scenario: An extra hot tea with 1 sugar 11 | 12 | When I order an extra hot "Tea" with 1 sugar 13 | Then the instruction generated should be "Th:1:0" 14 | 15 | @takeOrder @orangeJuice @protocol 16 | Scenario: An Orange juice 17 | 18 | When I order an "Orange Juice" 19 | Then the instruction generated should be "O::" 20 | 21 | @takeOrder @orangeJuice @sugar @protocol 22 | @wip 23 | Scenario: Extra sugar with Orange Juice is ignored 24 | 25 | When I order an "Orange Juice" with 1 sugar 26 | Then the instruction generated should be "O::" 27 | 28 | @takeOrder @orangeJuice @sugar @extraHot @protocol 29 | Scenario: Extra hot with Orange Juice is ignored 30 | 31 | When I order an extra hot "Orange Juice" with 1 sugar 32 | Then the instruction generated should be "O::" 33 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/game-of-life/board.feature: -------------------------------------------------------------------------------- 1 | Feature: Petri board of the game of life should... 2 | 3 | 4 | In order to play a game of life 5 | As a computer scientist 6 | I need to be able to setup the board and click start 7 | 8 | @ui @toggle 9 | Scenario: Click on a dead cell 10 | Given a board 11 | When I click on a dead cell 12 | Then it should come to life 13 | 14 | 15 | @ui @toggle 16 | Scenario: Click on an alive cell 17 | Given a board 18 | When I click on an alive cell 19 | Then it should kill the cell 20 | 21 | @toggle 22 | Scenario: Toggle a cell on the board 23 | 24 | Given a 5 by 5 game 25 | When I toggle the cell at (2, 3) 26 | Then the grid should look like 27 | """..... 28 | ..... 29 | ..... 30 | ..X.. 31 | .....""" 32 | When I toggle the cell at (2, 4) 33 | Then the grid should look like 34 | """..... 35 | ..... 36 | ..... 37 | ..X.. 38 | ..X..""" 39 | When I toggle the cell at (2, 3) 40 | Then the grid should look like 41 | """..... 42 | ..... 43 | ..... 44 | ..... 45 | ..X..""" -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/java/GrammarParserStatisticsListener.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.java; 2 | 3 | import com.thoughtworks.qdox.model.JavaClass; 4 | import com.thoughtworks.qdox.model.JavaMethod; 5 | import com.thoughtworks.qdox.model.JavaPackage; 6 | 7 | /** 8 | * @author @aloyer 9 | */ 10 | public class GrammarParserStatisticsListener extends GrammarParserListenerAdapter { 11 | private int packagesParsed = 0; 12 | private int classesParsed = 0; 13 | private int methodsParsed = 0; 14 | 15 | public int numberOfClassesParsed() { 16 | return classesParsed; 17 | } 18 | 19 | public int numberOfPackagesParsed() { 20 | return packagesParsed; 21 | } 22 | 23 | public int numberOfMethodsParsed() { 24 | return methodsParsed; 25 | } 26 | 27 | @Override 28 | public void exitingPackage(JavaPackage pkg) { 29 | packagesParsed++; 30 | } 31 | 32 | @Override 33 | public void exitingClass(JavaClass klazz) { 34 | classesParsed++; 35 | } 36 | 37 | @Override 38 | public void exitingMethod(JavaMethod method) { 39 | methodsParsed++; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/java/GrammarParserListenerAdapter.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.java; 2 | 3 | import com.thoughtworks.qdox.model.JavaClass; 4 | import com.thoughtworks.qdox.model.JavaMethod; 5 | import com.thoughtworks.qdox.model.JavaPackage; 6 | 7 | import java.util.Collection; 8 | 9 | /** 10 | * @author @aloyer 11 | */ 12 | public class GrammarParserListenerAdapter implements GrammarParserListener { 13 | @Override 14 | public void aboutToParsePackages(Collection packages) { 15 | } 16 | 17 | @Override 18 | public void aboutToParseClasses(Collection classes) { 19 | } 20 | 21 | @Override 22 | public void enteringPackage(JavaPackage pkg) { 23 | } 24 | 25 | @Override 26 | public void exitingPackage(JavaPackage pkg) { 27 | } 28 | 29 | @Override 30 | public void enteringClass(JavaClass klazz) { 31 | } 32 | 33 | @Override 34 | public void exitingClass(JavaClass klazz) { 35 | } 36 | 37 | @Override 38 | public void enteringMethod(JavaMethod method) { 39 | } 40 | 41 | @Override 42 | public void exitingMethod(JavaMethod method) { 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tzatziki-core/src/test/java/tzatziki/util/PackagePathTest.java: -------------------------------------------------------------------------------- 1 | package tzatziki.util; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | import static tzatziki.util.PackagePath.directSubPackageOf; 7 | 8 | 9 | public class PackagePathTest { 10 | 11 | @Test 12 | public void directSubPackageOf_should_work___() { 13 | assertThat(directSubPackageOf("", "tzatziki.util.matcher.string")).isEqualTo("tzatziki"); 14 | assertThat(directSubPackageOf("tzatziki", "tzatziki.util")).isEqualTo("util"); 15 | assertThat(directSubPackageOf("tzatziki.util", "tzatziki.util.matcher")).isEqualTo("matcher"); 16 | assertThat(directSubPackageOf("tzatziki.util", "tzatziki.util.matcher.string")).isEqualTo("matcher"); 17 | } 18 | 19 | @Test 20 | public void directSubPackageOf_should_return_null_when_same_packages_are_provided() { 21 | assertThat(directSubPackageOf("tzatziki.pdf", "tzatziki.pdf")).isNull(); 22 | } 23 | 24 | @Test(expected = IllegalArgumentException.class) 25 | public void directSubPackageOf_should_throw_when_packages_does_not_belong_to_the_same_tree() { 26 | directSubPackageOf("tzatziki.pdf", "tzatziki.util"); 27 | } 28 | } -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/step/ScenarioOutline.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.step; 2 | 3 | import com.google.common.collect.Lists; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author @aloyer 9 | */ 10 | public class ScenarioOutline { 11 | 12 | private String visualName; 13 | private List tags = Lists.newArrayList(); 14 | private List exampleScenarios = Lists.newArrayList(); 15 | 16 | public void setVisualName(String visualName) { 17 | this.visualName = visualName; 18 | } 19 | 20 | public String getVisualName() { 21 | return visualName; 22 | } 23 | 24 | public void add(Scenario exampleScenario) { 25 | exampleScenarios.add(exampleScenario); 26 | } 27 | 28 | public void traverse(FeatureVisitor visitor) { 29 | visitor.enterScenarioOutline(this); 30 | for (Scenario scenario : exampleScenarios) 31 | scenario.traverse(visitor); 32 | visitor.exitScenarioOutline(this); 33 | } 34 | 35 | public void addTags(List tags) { 36 | this.tags.addAll(tags); 37 | } 38 | 39 | public List getTags() { 40 | return tags; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/tag/TagDictionaryLoader.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.tag; 2 | 3 | import org.apache.commons.io.IOUtils; 4 | import tzatziki.util.PropertiesLoader; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.UnsupportedEncodingException; 9 | import java.net.URL; 10 | import java.util.Properties; 11 | 12 | /** 13 | * @author @aloyer 14 | */ 15 | public class TagDictionaryLoader { 16 | public TagDictionary fromUTF8PropertiesResource(String resourcePath) throws IOException { 17 | URL resource = PropertiesLoader.class.getResource(resourcePath); 18 | if (resource == null) 19 | throw new IllegalArgumentException("Resource not found '" + resourcePath + "'"); 20 | 21 | InputStream stream = resource.openStream(); 22 | try { 23 | Properties properties = new PropertiesLoader().loadFromUTF8Stream(stream); 24 | 25 | return new TagDictionary().declareTags(properties); 26 | } catch (UnsupportedEncodingException e) { 27 | throw new RuntimeException("UTF8 not supported...", e); 28 | } finally { 29 | IOUtils.closeQuietly(stream); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/util/MemoizableIterator.java: -------------------------------------------------------------------------------- 1 | package tzatziki.util; 2 | 3 | import java.util.Iterator; 4 | 5 | /** 6 | * @author @aloyer 7 | */ 8 | public class MemoizableIterator implements Iterator { 9 | 10 | public static MemoizableIterator wrap(Iterator iterator) { 11 | return new MemoizableIterator(iterator); 12 | } 13 | 14 | 15 | private final Iterator delegate; 16 | private boolean currentInitialized = false; 17 | private T current; 18 | 19 | public MemoizableIterator(Iterator delegate) { 20 | this.delegate = delegate; 21 | } 22 | 23 | @Override 24 | public boolean hasNext() { 25 | return delegate.hasNext(); 26 | } 27 | 28 | @Override 29 | public T next() { 30 | current = delegate.next(); 31 | currentInitialized = true; 32 | return current; 33 | } 34 | 35 | @Override 36 | public void remove() { 37 | throw new UnsupportedOperationException(); 38 | } 39 | 40 | public T current() { 41 | if(!currentInitialized) 42 | throw new IllegalStateException("Invoke next() at least once"); 43 | return current; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /tzatziki-core/src/test/java/tzatziki/TestSettings.java: -------------------------------------------------------------------------------- 1 | package tzatziki; 2 | 3 | import org.apache.commons.io.IOUtils; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.util.Properties; 8 | 9 | /** 10 | * @author @aloyer 11 | */ 12 | public class TestSettings { 13 | 14 | private Properties properties; 15 | 16 | public TestSettings() { 17 | } 18 | 19 | public String getBuildDir() { 20 | return getProperties().getProperty("buildDir"); 21 | } 22 | 23 | public String getBaseDir() { 24 | return getProperties().getProperty("baseDir"); 25 | } 26 | 27 | public Properties getProperties() { 28 | if (properties == null) { 29 | properties = new Properties(); 30 | InputStream stream = null; 31 | try { 32 | stream = getClass().getResourceAsStream("/test-settings.properties"); 33 | properties.load(stream); 34 | } catch (IOException e) { 35 | throw new RuntimeException("Failed to open settings", e); 36 | } finally { 37 | IOUtils.closeQuietly(stream); 38 | } 39 | } 40 | return properties; 41 | } 42 | } -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/util/PackagePath.java: -------------------------------------------------------------------------------- 1 | package tzatziki.util; 2 | 3 | /** 4 | * @author @aloyer 5 | */ 6 | public class PackagePath { 7 | 8 | public static boolean isSubPackageOf(String parent, String packageName) { 9 | return packageName.startsWith(parent) 10 | && !areSamePackage(parent, packageName); 11 | } 12 | 13 | public static String directSubPackageOf(String parent, String packageName) { 14 | if (areSamePackage(parent, packageName)) 15 | return null; 16 | if (!isSubPackageOf(parent, packageName)) 17 | throw new IllegalArgumentException("Package '" + packageName + "' is not a subPackage of '" + parent + "'"); 18 | 19 | int dec = parent.length(); 20 | if (dec > 0) 21 | dec++; // add '.' 22 | 23 | String subTree = packageName.substring(dec); 24 | int nextPkg = subTree.indexOf('.'); 25 | if (nextPkg < 0) 26 | return subTree; 27 | else 28 | return subTree.substring(0, nextPkg); 29 | } 30 | 31 | public static boolean areSamePackage(String packageName1, String packageName2) { 32 | return packageName1.equals(packageName2); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tzatziki-pdf/src/test/java/tzatziki/pdf/TestSettings.java: -------------------------------------------------------------------------------- 1 | package tzatziki.pdf; 2 | 3 | import org.apache.commons.io.IOUtils; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.util.Properties; 8 | 9 | /** 10 | * @author @aloyer 11 | */ 12 | public class TestSettings { 13 | 14 | private Properties properties; 15 | 16 | public TestSettings() { 17 | } 18 | 19 | public String getBuildDir() { 20 | return getProperties().getProperty("buildDir"); 21 | } 22 | 23 | public String getBaseDir() { 24 | return getProperties().getProperty("baseDir"); 25 | } 26 | 27 | public Properties getProperties() { 28 | if (properties == null) { 29 | properties = new Properties(); 30 | InputStream stream = null; 31 | try { 32 | stream = getClass().getResourceAsStream("/test-settings.properties"); 33 | properties.load(stream); 34 | } catch (IOException e) { 35 | throw new RuntimeException("Failed to open settings", e); 36 | } finally { 37 | IOUtils.closeQuietly(stream); 38 | } 39 | } 40 | return properties; 41 | } 42 | } -------------------------------------------------------------------------------- /tzatziki-samples/src/test/java/samples/TestSettings.java: -------------------------------------------------------------------------------- 1 | package samples; 2 | 3 | import org.apache.commons.io.IOUtils; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.util.Properties; 8 | 9 | /** 10 | * @author @aloyer 11 | */ 12 | public class TestSettings { 13 | 14 | private Properties properties; 15 | 16 | public TestSettings() { 17 | } 18 | 19 | public String getBuildDir() { 20 | return getProperties().getProperty("buildDir"); 21 | } 22 | 23 | public String getBaseDir() { 24 | return getProperties().getProperty("baseDir"); 25 | } 26 | 27 | public Properties getProperties() { 28 | if (properties == null) { 29 | properties = new Properties(); 30 | InputStream stream = null; 31 | try { 32 | stream = getClass().getResourceAsStream("/test-settings.properties"); 33 | properties.load(stream); 34 | } catch (IOException e) { 35 | throw new RuntimeException("Failed to open settings", e); 36 | } finally { 37 | IOUtils.closeQuietly(stream); 38 | } 39 | } 40 | return properties; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/exec/gson/ScenarioOutlineExecSerializer.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec.gson; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonObject; 6 | import com.google.gson.JsonSerializationContext; 7 | import com.google.gson.JsonSerializer; 8 | import tzatziki.analysis.exec.model.ScenarioOutlineExec; 9 | 10 | import java.lang.reflect.Type; 11 | 12 | import static tzatziki.analysis.exec.gson.StepContainerDeserializer.SCENARIO_OUTLINE; 13 | import static tzatziki.analysis.exec.gson.StepContainerDeserializer.TYPE; 14 | 15 | /** 16 | * @author @aloyer 17 | */ 18 | public class ScenarioOutlineExecSerializer implements JsonSerializer { 19 | 20 | private final Gson delegate; 21 | 22 | public ScenarioOutlineExecSerializer(Gson delegate) { 23 | this.delegate = delegate; 24 | } 25 | 26 | @Override 27 | public JsonElement serialize(ScenarioOutlineExec src, Type typeOfSrc, JsonSerializationContext context) { 28 | JsonObject serialized = delegate.toJsonTree(src, typeOfSrc).getAsJsonObject(); 29 | serialized.addProperty(TYPE, SCENARIO_OUTLINE); 30 | return serialized; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tzatziki-core/src/test/java/tzatziki/util/LoadJson.java: -------------------------------------------------------------------------------- 1 | package tzatziki.util; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import org.apache.commons.io.IOUtils; 6 | 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.InputStreamReader; 10 | import java.io.Reader; 11 | import java.io.StringReader; 12 | 13 | /** 14 | * @author @aloyer 15 | */ 16 | public class LoadJson { 17 | private String charsetName = "UTF8"; 18 | 19 | public JsonNode loadFromResource(String resourceName) throws IOException { 20 | InputStream in = getClass().getResourceAsStream(resourceName); 21 | try { 22 | Reader reader = new InputStreamReader(in, charsetName); 23 | ObjectMapper mapper = new ObjectMapper(); 24 | return mapper.readValue(reader, JsonNode.class); 25 | } finally { 26 | IOUtils.closeQuietly(in); 27 | } 28 | } 29 | 30 | public JsonNode loadFromString(String content) throws IOException { 31 | Reader reader = new StringReader(content); 32 | ObjectMapper mapper = new ObjectMapper(); 33 | return mapper.readValue(reader, JsonNode.class); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/java/samples/coffeemachine/Gateway.java: -------------------------------------------------------------------------------- 1 | package samples.coffeemachine; 2 | 3 | /** 4 | * @author @aloyer 5 | */ 6 | public class Gateway { 7 | 8 | private DrinkMaker drinkMaker; 9 | 10 | public Gateway(DrinkMaker drinkMaker) { 11 | this.drinkMaker = drinkMaker; 12 | } 13 | 14 | 15 | public void order(String drinkType, int nbSugar, boolean b) { 16 | StringBuilder c = new StringBuilder(); 17 | 18 | if (drinkType.equalsIgnoreCase("Coffee")) 19 | c.append("C"); 20 | else if (drinkType.equalsIgnoreCase("Tea")) 21 | c.append("T"); 22 | else if (drinkType.equalsIgnoreCase("Chocolate")) 23 | c.append("H"); 24 | else if (drinkType.equalsIgnoreCase("Orange Juice")) 25 | c.append("O"); 26 | 27 | 28 | if (nbSugar > 0) { 29 | c.append(":").append(nbSugar).append(":0"); 30 | } else 31 | c.append("::"); 32 | 33 | drinkMaker.executeCommand(c.toString()); 34 | } 35 | 36 | public void publish(String message) { 37 | if(message.contains("enough")) 38 | throw new IllegalArgumentException(); 39 | drinkMaker.executeCommand("M:" + message); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tzatziki-pdf/src/main/java/tzatziki/pdf/emitter/EmbeddedEmitter.java: -------------------------------------------------------------------------------- 1 | package tzatziki.pdf.emitter; 2 | 3 | import com.itextpdf.text.Paragraph; 4 | import gutenberg.itext.Emitter; 5 | import gutenberg.itext.ITextContext; 6 | import gutenberg.itext.model.SourceCode; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import tzatziki.analysis.exec.model.Embedded; 10 | 11 | /** 12 | * @author @aloyer 13 | */ 14 | public class EmbeddedEmitter implements Emitter { 15 | 16 | private Logger log = LoggerFactory.getLogger(EmbeddedEmitter.class); 17 | 18 | @Override 19 | public void emit(Embedded value, ITextContext emitterContext) { 20 | String mimeType = value.mimeType(); 21 | if (mimeType.equalsIgnoreCase(SourceCode.MIME_TYPE)) { 22 | SourceCode sourceCode = SourceCode.fromBytes(value.data()); 23 | emitterContext.emit(sourceCode); 24 | } else if (mimeType.startsWith("plain/text")) { 25 | String text = value.isText() ? value.text() : new String(value.data()); 26 | Paragraph p = new Paragraph(text); 27 | emitterContext.emit(p); 28 | } else { 29 | log.warn("Unsupported mime type {}, data discarded", mimeType); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tzatziki-pdf/src/main/java/tzatziki/pdf/EmitterContext.java: -------------------------------------------------------------------------------- 1 | package tzatziki.pdf; 2 | 3 | import com.itextpdf.text.Document; 4 | import com.itextpdf.text.pdf.PdfWriter; 5 | import gutenberg.itext.ITextContext; 6 | import gutenberg.itext.Sections; 7 | import gutenberg.itext.Styles; 8 | 9 | /** 10 | * @author @aloyer 11 | */ 12 | public class EmitterContext { 13 | private final ITextContext context; 14 | private final Settings settings; 15 | private final Sections sections; 16 | private final Styles styles; 17 | 18 | public EmitterContext(ITextContext context, 19 | Settings settings, 20 | Sections sections, 21 | Styles styles) { 22 | this.context = context; 23 | this.settings = settings; 24 | this.sections = sections; 25 | this.styles = styles; 26 | } 27 | 28 | public ITextContext iTextContext() { 29 | return context; 30 | } 31 | 32 | public Styles styles() { 33 | return styles; 34 | } 35 | 36 | public Document getDocument() { 37 | return context.getDocument(); 38 | } 39 | 40 | public PdfWriter getPdfWriter() { 41 | return context.getPdfWriter(); 42 | } 43 | 44 | 45 | public Settings getSettings() { 46 | return settings; 47 | } 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/dinovet/features/manage_diagnosis_events.feature: -------------------------------------------------------------------------------- 1 | Feature: Manage diagnosis events 2 | In order to know what treatment will be necessary for a patient 3 | An employee 4 | wants to be able to add diagnoses for the patient 5 | 6 | Scenario: Add a new diagnosis event 7 | Given I have added a client and patient 8 | And I have created a diagnosis 9 | And I am on the new diagnosis event page 10 | And I am an employee 11 | When I select "Rabies" from "Diagnosis" 12 | And I fill in "Comment" with "This is a bizzare case of mutant Rabies" 13 | And I press "Make Diagnosis" 14 | Then I should see "Diagnosis recorded" 15 | And I should see "Rabies" 16 | 17 | Scenario: Add two diagnosis of the same type 18 | Given I have added a client and patient 19 | And I have created a diagnosis 20 | And I am on the new diagnosis event page 21 | And I am an employee 22 | When I select "Rabies" from "Diagnosis" 23 | And I fill in "Comment" with "This is a bizzare case of mutant Rabies" 24 | And I press "Make Diagnosis" 25 | And I go to the new diagnosis event page 26 | And I select "Rabies" from "Diagnosis" 27 | And I fill in "Comment" with "This is a bizzare case of mutant Rabies" 28 | And I press "Make Diagnosis" 29 | Then I should see "Diagnosis recorded" 30 | And I should see "Rabies" 31 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/exec/model/ScenarioRef.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec.model; 2 | 3 | /** 4 | * @author @aloyer 5 | */ 6 | public class ScenarioRef { 7 | private final String featureUri; 8 | private final String featureName; 9 | private final String scenarioName; 10 | private final LineRange lineRange; 11 | 12 | public ScenarioRef(String featureUri, String featureName, LineRange lineRange, String scenarioName) { 13 | this.featureUri = featureUri; 14 | this.featureName = featureName; 15 | this.scenarioName = scenarioName; 16 | this.lineRange = lineRange; 17 | } 18 | 19 | public String featureUri() { 20 | return featureUri; 21 | } 22 | 23 | public String featureName() { 24 | return featureName; 25 | } 26 | 27 | public String scenarioName() { 28 | return scenarioName; 29 | } 30 | 31 | public LineRange scenarioLineRange() { 32 | return lineRange; 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return "ScenarioRef{" + 38 | "featureUri='" + featureUri + '\'' + 39 | ", featureName='" + featureName + '\'' + 40 | ", scenarioName='" + scenarioName + '\'' + 41 | ", lineRange=" + lineRange + 42 | '}'; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/exec/model/MatchExec.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec.model; 2 | 3 | import com.google.common.collect.FluentIterable; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author @aloyer 9 | */ 10 | public class MatchExec { 11 | private final String location; 12 | private final List arguments; 13 | 14 | public MatchExec(String location, List arguments) { 15 | this.location = location; 16 | this.arguments = arguments; 17 | } 18 | 19 | public FluentIterable getArgs() { 20 | return FluentIterable.from(arguments); 21 | } 22 | 23 | public String getLocation() { 24 | return location; 25 | } 26 | 27 | public MatchExec recursiveCopy() { 28 | // TODO find a suitable to ensure this is still valid 29 | // or a real copy is made if a field becomes mutable 30 | return this; 31 | } 32 | 33 | public static class Arg { 34 | public final String val; 35 | public final Integer offset; 36 | 37 | public Arg(String val, Integer offset) { 38 | 39 | this.val = val; 40 | this.offset = offset; 41 | } 42 | 43 | public Integer getOffset() { 44 | return offset; 45 | } 46 | 47 | public String getVal() { 48 | return val; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/stylius/backend/taxation_settings.feature: -------------------------------------------------------------------------------- 1 | @taxation 2 | Feature: Taxation settings 3 | In order configure my store taxation system 4 | As a store owner 5 | I want to be able to edit taxation configuration 6 | 7 | Background: 8 | Given I am logged in as administrator 9 | And the following zones are defined: 10 | | name | type | members | 11 | | German lands | country | Germany, Austria, Switzerland | 12 | | USA | country | United States | 13 | 14 | Scenario: Accessing the settings form 15 | Given I am on the dashboard page 16 | When I follow "Taxation settings" 17 | Then I should be on the taxation settings page 18 | 19 | Scenario: Saving the configuration 20 | Given I am on the taxation settings page 21 | When I press "Save changes" 22 | Then I should still be on the taxation settings page 23 | And I should see "Settings have been successfully updated." 24 | 25 | Scenario: Editing the default tax zone 26 | Given I am on the taxation settings page 27 | When I select "USA" from "Default tax zone" 28 | And I press "Save changes" 29 | Then I should still be on the taxation settings page 30 | And I should see "Settings have been successfully updated." 31 | -------------------------------------------------------------------------------- /tzatziki-core/src/test/resources/tzatziki/analysis/step/subdomain/running-out.feature: -------------------------------------------------------------------------------- 1 | Feature: Running Out 2 | 3 | # **In order to** never run out beverage 4 | # 5 | # **As a** shop keeper 6 | # 7 | # **I want to** be informed that there is a shortage and to send a email notification 8 | # to the company so that they can come and refill the machine. 9 | # 10 | # The users of the coffee machine are complaining that there is often shortages 11 | # of water and/or milk. It takes weeks before the machine is refilled. 12 | # Your product owner wants to you to take advantage of the machine capabilities 13 | # to inform the user that there is a shortage 14 | 15 | @Notification 16 | Scenario: Last Coffee 17 | Given no more "Coffee" remaining in the machine 18 | When I order a "Coffee" with 1 sugar 19 | Then a mail should have been sent indicating "Coffee" is running out 20 | 21 | @Notification 22 | Scenario Outline: Last beverage 23 | Given no more "" remaining in the machine 24 | When I order a "" 25 | Then a mail should have been sent indicating "" is running out 26 | 27 | Examples: 28 | | drink | 29 | | Orange juice | 30 | | Tea | 31 | | Chocolate | 32 | 33 | @manual 34 | @Notification 35 | Scenario: Manually send an email 36 | Given an empty machine 37 | When I click on the "Send Test Email" button 38 | Then a test mail should have been sent -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/exec/ExecutionFilter.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec; 2 | 3 | import com.google.common.base.Optional; 4 | import com.google.common.base.Predicate; 5 | import com.google.common.collect.FluentIterable; 6 | import tzatziki.analysis.exec.model.FeatureExec; 7 | import tzatziki.analysis.exec.tag.TagFilter; 8 | import tzatziki.analysis.exec.tag.Tags; 9 | 10 | import java.util.Set; 11 | 12 | /** 13 | * @author @aloyer 14 | */ 15 | public class ExecutionFilter { 16 | 17 | private final TagFilter tagFilter; 18 | 19 | public ExecutionFilter(TagFilter tagFilter) { 20 | this.tagFilter = tagFilter; 21 | } 22 | 23 | public Optional filter(FeatureExec feature) { 24 | FluentIterable inheritedTags = feature.tags(); 25 | FeatureExec copy = feature.recursiveCopy(matching(inheritedTags.toSet(), tagFilter)); 26 | 27 | if (copy.scenario().isEmpty()) 28 | return Optional.absent(); 29 | else 30 | return Optional.of(copy); 31 | } 32 | 33 | private static Predicate matching(final Set inheritedTags, final TagFilter tagFilter) { 34 | return new Predicate() { 35 | @Override 36 | public boolean apply(Tags tags) { 37 | return tagFilter.apply(tags.completeWith(inheritedTags)); 38 | } 39 | }; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/util/PropertiesLoader.java: -------------------------------------------------------------------------------- 1 | package tzatziki.util; 2 | 3 | import org.apache.commons.io.IOUtils; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.InputStreamReader; 8 | import java.io.UnsupportedEncodingException; 9 | import java.net.URL; 10 | import java.util.Properties; 11 | 12 | /** 13 | * @author @aloyer 14 | */ 15 | public class PropertiesLoader { 16 | public Properties loadFromUTF8Resource(String resourcePath) throws IOException { 17 | URL resource = PropertiesLoader.class.getResource(resourcePath); 18 | if (resource == null) 19 | throw new IllegalArgumentException("Resource not found " + resource); 20 | 21 | InputStream stream = resource.openStream(); 22 | try { 23 | return loadFromUTF8Stream(stream); 24 | } catch (UnsupportedEncodingException e) { 25 | throw new RuntimeException("UTF8 not supported...", e); 26 | } finally { 27 | IOUtils.closeQuietly(stream); 28 | } 29 | } 30 | 31 | public Properties loadFromUTF8Stream(InputStream stream) throws IOException { 32 | if (stream == null) 33 | throw new IllegalArgumentException("No stream provided"); 34 | 35 | Properties properties = new Properties(); 36 | properties.load(new InputStreamReader(stream, "UTF8")); 37 | return properties; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tzatziki-pdf/src/main/java/tzatziki/pdf/emitter/TagsEmitter.java: -------------------------------------------------------------------------------- 1 | package tzatziki.pdf.emitter; 2 | 3 | import com.google.common.collect.FluentIterable; 4 | import com.itextpdf.text.Chunk; 5 | import com.itextpdf.text.Font; 6 | import com.itextpdf.text.Paragraph; 7 | import gutenberg.itext.Emitter; 8 | import gutenberg.itext.ITextContext; 9 | import gutenberg.itext.Styles; 10 | import tzatziki.pdf.Settings; 11 | import tzatziki.pdf.model.Tags; 12 | 13 | /** 14 | * @author @aloyer 15 | */ 16 | public class TagsEmitter implements Emitter { 17 | public static final String TAG_FONT = "tag-font"; 18 | 19 | @Override 20 | public void emit(Tags tagContainer, ITextContext emitterContext) { 21 | FluentIterable tags = tagContainer.tags(); 22 | if (tags.isEmpty()) 23 | return; 24 | 25 | Styles styles = emitterContext.keyValues().getNullable(Styles.class).get(); 26 | 27 | Paragraph pTags = new Paragraph("Tags: ", styles.getFontOrDefault(Settings.META_FONT)); 28 | boolean first = true; 29 | Font tagFont = styles.getFontOrDefault(TAG_FONT); 30 | for (String text : tags) { 31 | if (first) { 32 | first = false; 33 | } else { 34 | text = ", " + text; 35 | } 36 | 37 | pTags.add(new Chunk(text, tagFont)); 38 | } 39 | 40 | emitterContext.append(pTags); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/util/Filters.java: -------------------------------------------------------------------------------- 1 | package tzatziki.util; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * @author @aloyer 7 | */ 8 | public class Filters { 9 | public static Filter identity() { 10 | return new Filter() { 11 | @Override 12 | public T filter(T value) { 13 | return value; 14 | } 15 | }; 16 | } 17 | 18 | public static Filter chain(final Filter... filters) { 19 | if (filters.length == 1) 20 | return filters[0]; 21 | return new Filter() { 22 | @Override 23 | public T filter(T value) { 24 | T processed = value; 25 | for (Filter filter : filters) { 26 | processed = filter.filter(processed); 27 | } 28 | return processed; 29 | } 30 | }; 31 | } 32 | 33 | public static Filter chain(final List> filters) { 34 | if (filters.size() == 1) 35 | return filters.get(0); 36 | return new Filter() { 37 | @Override 38 | public T filter(T value) { 39 | T processed = value; 40 | for (Filter filter : filters) { 41 | processed = filter.filter(processed); 42 | } 43 | return processed; 44 | } 45 | }; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/exec/model/DataTable.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec.model; 2 | 3 | import com.google.common.collect.FluentIterable; 4 | import com.google.common.collect.Lists; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author @aloyer 10 | */ 11 | public class DataTable { 12 | public List rows = Lists.newArrayList(); 13 | 14 | public void declareRow(Row row) { 15 | rows.add(row); 16 | } 17 | 18 | public boolean isEmpty() { 19 | return rows.isEmpty(); 20 | } 21 | 22 | public int nbColumns() { 23 | if (isEmpty()) 24 | throw new IllegalStateException("Table is empty"); 25 | return rows.get(0).nbColumns(); 26 | } 27 | 28 | public FluentIterable rows() { 29 | return FluentIterable.from(rows); 30 | } 31 | 32 | public static class Row { 33 | private List cells; 34 | private List comments; 35 | 36 | public Row(List cells, List comments) { 37 | this.cells = cells; 38 | this.comments = comments; 39 | } 40 | 41 | public FluentIterable cells() { 42 | return FluentIterable.from(cells); 43 | } 44 | 45 | public FluentIterable comments() { 46 | return FluentIterable.from(comments); 47 | } 48 | 49 | public int nbColumns() { 50 | return cells.size(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tzatziki-core/src/test/java/tzatziki/analysis/exec/tag/TagViewTest.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec.tag; 2 | 3 | import org.junit.Test; 4 | import tzatziki.analysis.exec.gson.JsonIO; 5 | import tzatziki.analysis.exec.model.FeatureExec; 6 | import tzatziki.analysis.exec.support.TagView; 7 | 8 | import java.io.InputStream; 9 | import java.io.UnsupportedEncodingException; 10 | import java.util.List; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | 14 | public class TagViewTest { 15 | 16 | @Test 17 | public void usecase() throws Exception { 18 | List features = loadSample(); 19 | 20 | TagView tagView = new TagView("Non wip about tea", TagFilter.from("~@wip", "@tea")); 21 | for (FeatureExec featureExec : features) { 22 | tagView.consolidateView(featureExec); 23 | } 24 | 25 | assertThat(tagView.scenarioMatching().size()).isEqualTo(11); 26 | assertThat(tagView.scenarioFailed().size()).isEqualTo(1); 27 | assertThat(tagView.scenarioPassed().size()).isEqualTo(1); 28 | assertThat(tagView.scenarioPending().size()).isEqualTo(1); 29 | assertThat(tagView.scenarioUndefined().size()).isEqualTo(8); 30 | assertThat(tagView.scenarioSkipped().size()).isEqualTo(0); 31 | } 32 | 33 | private List loadSample() throws UnsupportedEncodingException { 34 | InputStream in = getClass().getResourceAsStream("/tzatziki/analysis/exec/tag/coffeemachine-exec.json"); 35 | return new JsonIO().load(in); 36 | } 37 | } -------------------------------------------------------------------------------- /tzatziki-core/src/test/resources/tzatziki/junit/coffeemachine/05-running-out.feature: -------------------------------------------------------------------------------- 1 | Feature: Running Out 2 | 3 | # **In order to** never run out beverage 4 | # 5 | # **As a** shop keeper 6 | # 7 | # **I want to** be informed that there is a shortage and to send a email notification 8 | # to the company so that they can come and refill the machine. 9 | # 10 | # The users of the coffee machine are complaining that there is often shortages 11 | # of water and/or milk. It takes weeks before the machine is refilled. 12 | # Your product owner wants to you to take advantage of the machine capabilities 13 | # to inform the user that there is a shortage 14 | 15 | @notification @runningOut 16 | @coffee @sugar @takeOrder 17 | Scenario: Last Coffee 18 | Given no more "Coffee" remaining in the machine 19 | When I order a "Coffee" with 1 sugar 20 | Then a mail should have been sent indicating "Coffee" is running out 21 | 22 | @notification @runningOut 23 | @orangeJuice @tea @chocolate @takeOrder 24 | Scenario Outline: Last beverage 25 | Given no more "" remaining in the machine 26 | When I order a "" 27 | Then a mail should have been sent indicating "" is running out 28 | 29 | Examples: 30 | | drink | 31 | | Orange juice | 32 | | Tea | 33 | | Chocolate | 34 | 35 | @manual 36 | @notification @noDrink 37 | Scenario: Manually send an email 38 | Given an empty machine 39 | When I click on the "Send Test Email" button 40 | Then a test mail should have been sent -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/coffeemachine/05-running-out.feature: -------------------------------------------------------------------------------- 1 | Feature: Running Out 2 | 3 | # **In order to** never run out beverage 4 | # 5 | # **As a** shop keeper 6 | # 7 | # **I want to** be informed that there is a shortage and to send a email notification 8 | # to the company so that they can come and refill the machine. 9 | # 10 | # The users of the coffee machine are complaining that there is often shortages 11 | # of water and/or milk. It takes weeks before the machine is refilled. 12 | # Your product owner wants to you to take advantage of the machine capabilities 13 | # to inform the user that there is a shortage 14 | 15 | @notification @runningOut 16 | @coffee @sugar @takeOrder 17 | Scenario: Last Coffee 18 | Given no more "Coffee" remaining in the machine 19 | When I order a "Coffee" with 1 sugar 20 | Then a mail should have been sent indicating "Coffee" is running out 21 | 22 | @notification @runningOut 23 | @orangeJuice @tea @chocolate @takeOrder 24 | Scenario Outline: Last beverage 25 | Given no more "" remaining in the machine 26 | When I order a "" 27 | Then a mail should have been sent indicating "" is running out 28 | 29 | Examples: 30 | | drink | 31 | | Orange juice | 32 | | Tea | 33 | | Chocolate | 34 | 35 | @manual 36 | @notification @noDrink 37 | Scenario: Manually send an email 38 | Given an empty machine 39 | When I click on the "Send Test Email" button 40 | Then a test mail should have been sent -------------------------------------------------------------------------------- /tzatziki-pdf/src/main/java/tzatziki/pdf/emitter/DefaultPdfEmitters.java: -------------------------------------------------------------------------------- 1 | package tzatziki.pdf.emitter; 2 | 3 | import gutenberg.itext.ITextContext; 4 | import tzatziki.analysis.exec.model.BackgroundExec; 5 | import tzatziki.analysis.exec.model.Embedded; 6 | import tzatziki.analysis.exec.model.FeatureExec; 7 | import tzatziki.analysis.exec.model.ScenarioExec; 8 | import tzatziki.analysis.exec.support.TagViews; 9 | import tzatziki.analysis.java.Grammar; 10 | import tzatziki.analysis.tag.TagDictionary; 11 | import tzatziki.pdf.model.ScenarioOutlineWithResolved; 12 | import tzatziki.pdf.model.Steps; 13 | import tzatziki.pdf.model.Tags; 14 | 15 | /** 16 | * @author @aloyer 17 | */ 18 | public class DefaultPdfEmitters { 19 | public void registerDefaults(ITextContext context) { 20 | context.register(FeatureExec.class, new FeatureEmitter()); 21 | context.register(ScenarioExec.class, new ScenarioEmitter()); 22 | context.register(BackgroundExec.class, new BackgroundEmitter()); 23 | context.register(ScenarioOutlineWithResolved.class, new ScenarioOutlineEmitter()); 24 | context.register(Steps.class, new StepsEmitter()); 25 | context.register(Tags.class, new TagsEmitter()); 26 | context.register(TagDictionary.class, new TagDictionaryEmitter()); 27 | context.register(TagViews.class, new TagViewsEmitter()); 28 | context.register(Embedded.class, new EmbeddedEmitter()); 29 | context.register(Grammar.class, new GrammarEmitter()); 30 | } 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/stylius/frontend/currencies.feature: -------------------------------------------------------------------------------- 1 | @currencies 2 | Feature: Currencies 3 | In order to buy products paying in different currencies 4 | As a visitor or as a logged in user 5 | I need to be able to switch between multiple currencies 6 | 7 | Background: 8 | Given there are following taxonomies defined: 9 | | name | 10 | | Category | 11 | And taxonomy "Category" has following taxons: 12 | | Clothing > PHP T-Shirts | 13 | And the following products exist: 14 | | name | price | taxons | 15 | | PHP Top | 5.99 | PHP T-Shirts | 16 | And there are following exchange rates: 17 | | currency | rate | 18 | | EUR | 1 | 19 | | USD | 0.76496 | 20 | | GBP | 1.13986 | 21 | 22 | Scenario: Switching currency as visitor 23 | Given I am on the store homepage 24 | When I follow "£" 25 | Then I should see product prices in "£" 26 | When I follow "$" 27 | Then I should see product prices in "$" 28 | When I follow "€" 29 | Then I should see product prices in "€" 30 | 31 | Scenario: Switching currency as logged in user 32 | Given I am logged in user 33 | And I am on the store homepage 34 | When I follow "£" 35 | Then I should see product prices in "£" 36 | When I follow "$" 37 | Then I should see product prices in "$" 38 | When I follow "£" 39 | Then I should see product prices in "£" 40 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/exec/model/ScenarioExec.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec.model; 2 | 3 | import com.google.common.base.Optional; 4 | import com.google.common.base.Predicate; 5 | import com.google.common.base.Predicates; 6 | import tzatziki.analysis.exec.tag.Tags; 7 | 8 | import static tzatziki.analysis.exec.model.StepExec.statusPassed; 9 | 10 | /** 11 | * @author @aloyer 12 | */ 13 | public class ScenarioExec extends StepContainer { 14 | private final String keyword; 15 | private final String name; 16 | 17 | public ScenarioExec(String keyword, String name) { 18 | this.keyword = keyword; 19 | this.name = name; 20 | } 21 | 22 | public String name() { 23 | return name; 24 | } 25 | 26 | public boolean isSucess() { 27 | return steps().allMatch(statusPassed); 28 | } 29 | 30 | public ScenarioExec recursiveCopy() { 31 | ScenarioExec copy = new ScenarioExec(keyword, name); 32 | recursiveCopy(copy); 33 | return copy; 34 | } 35 | 36 | public Status status() { 37 | Optional opt = steps().firstMatch(Predicates.not(StepExec.statusPassed)); 38 | if (opt.isPresent()) 39 | return opt.get().result().status(); 40 | else 41 | return Status.Passed; 42 | } 43 | 44 | public Optional recursiveCopy(Predicate matching) { 45 | if(matching.apply(Tags.from(tags().toList()))) { 46 | return Optional.of(recursiveCopy()); 47 | } 48 | return Optional.absent(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/coffeemachine/06-background.feature: -------------------------------------------------------------------------------- 1 | Feature: Making Even more Money 2 | 3 | # 4 | # **In order to** have daily reports of what is sold and when 5 | # 6 | # **As a** shop keeper 7 | # 8 | # **I want to** track statistics of machine usage 9 | # 10 | #```formula 11 | # {\eta \leq C(\delta(\eta) +\Lambda_M(0,\delta)) 12 | #``` 13 | # 14 | #```formula 15 | # \Re{z} =\frac{n\pi \dfrac{\theta +\psi}{2}}{ 16 | # \left(\dfrac{\theta +\psi}{2}\right)^2 + \left( \dfrac{1}{2} 17 | # \log \left\vert\dfrac{B}{A}\right\vert\right)^2}. 18 | #``` 19 | # 20 | 21 | Background: Default daily activity 22 | Given the following orders: 23 | | time | drink | 24 | | 08:05:23 | Coffee | 25 | | 08:06:43 | Coffee | 26 | | 08:10:23 | Coffee | 27 | | 08:45:03 | Tea | 28 | | 10:05:47 | Coffee | 29 | | 10:05:47 | Chocolate | 30 | 31 | # Represents the default daily activity 32 | # Basic statistics usage 33 | 34 | @reporting @noDrink 35 | Scenario: Statistics collect basic usage 36 | 37 | # **Report is queried** and generated on-the-fly. 38 | 39 | When I query for a report 40 | Then the report output should be 41 | """ 42 | chocolate: 1 43 | coffee: 4 44 | tea: 1 45 | --- 46 | Total: 3.00€ 47 | """ 48 | 49 | @reporting @noDrink 50 | Scenario: Statistics collect basic usage 51 | 52 | When I order a "Coffee" with 5 sugar 53 | And I query for a report 54 | Then the report output should be 55 | """ 56 | chocolate: 1 57 | coffee: 4 58 | tea: 1 59 | --- 60 | Total: 3.00€ 61 | """ 62 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/stylius/frontend/cart_inclusive_tax.feature: -------------------------------------------------------------------------------- 1 | @checkout 2 | Feature: Tax included in price 3 | In order to handle product taxation 4 | As a store owner 5 | I want to apply taxes during checkout 6 | 7 | Background: 8 | Given there are following taxonomies defined: 9 | | name | 10 | | Category | 11 | And taxonomy "Category" has following taxons: 12 | | Clothing > PHP T-Shirts | 13 | And the following zones are defined: 14 | | name | type | members | 15 | | Germany | country | Germany | 16 | And there are following tax categories: 17 | | name | 18 | | Taxable Goods | 19 | And the following tax rates exist: 20 | | category | zone | name | amount | included in price? | 21 | | Taxable Goods | Germany | Germany VAT | 23% | yes | 22 | And the following products exist: 23 | | name | price | taxons | tax category | 24 | | PHP Top | 85 | PHP T-Shirts | Taxable Goods | 25 | And the default tax zone is "Germany" 26 | 27 | Scenario: Correct amounts are displayed for inclusive taxes 28 | Given I am on the store homepage 29 | And I follow "PHP T-Shirts" 30 | And I click "PHP Top" 31 | When I fill in "Quantity" with "3" 32 | And I press "Add to cart" 33 | Then I should be on the cart summary page 34 | And "Tax total: €47.68" should appear on the page 35 | And "Grand total: €255.00" should appear on the page 36 | -------------------------------------------------------------------------------- /tzatziki-samples/src/test/java/samples/coffeemachine/CoffeeMachineTagCheckTest.java: -------------------------------------------------------------------------------- 1 | package samples.coffeemachine; 2 | 3 | import org.junit.runner.RunWith; 4 | import samples.TestSettings; 5 | import tzatziki.analysis.step.Features; 6 | import tzatziki.analysis.tag.TagDictionary; 7 | import tzatziki.junit.SanityTagChecker; 8 | 9 | import java.io.File; 10 | 11 | import static tzatziki.junit.SanityTagChecker.loadFeaturesFromSourceDirectory; 12 | 13 | /** 14 | * @author @aloyer 15 | */ 16 | @RunWith(SanityTagChecker.class) 17 | public class CoffeeMachineTagCheckTest { 18 | 19 | @SanityTagChecker.TagDictionaryProvider 20 | public static TagDictionary tagDictionary() { 21 | return new TagDictionary() 22 | .declareTag("@wip") 23 | .declareTag("@protocol") 24 | .declareTag("@notification") 25 | .declareTag("@message") 26 | .declareTag("@runningOut") 27 | .declareTag("@coffee") 28 | .declareTag("@tea") 29 | .declareTag("@chocolate") 30 | .declareTag("@sugar") 31 | .declareTag("@noSugar") 32 | .declareTag("@takeOrder") 33 | .declareTag("@payment") 34 | .declareTag("@reporting") 35 | .declareTag("@manual") 36 | ; 37 | } 38 | 39 | @SanityTagChecker.FeaturesProvider 40 | public static Features features() { 41 | String basedir = new TestSettings().getBaseDir(); 42 | return loadFeaturesFromSourceDirectory(new File(basedir, "src/main/resources/samples/coffeemachine")); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/stylius/frontend/checkout_finalize.feature: -------------------------------------------------------------------------------- 1 | @checkout 2 | Feature: Checkout finalization 3 | In order to buy products 4 | As a visitor 5 | I want to be able to complete the checkout process 6 | 7 | Background: 8 | Given there are following taxonomies defined: 9 | | name | 10 | | Category | 11 | And taxonomy "Category" has following taxons: 12 | | Clothing > PHP T-Shirts | 13 | And the following products exist: 14 | | name | price | taxons | 15 | | PHP Top | 5.99 | PHP T-Shirts | 16 | And the following zones are defined: 17 | | name | type | members | 18 | | UK | country | United Kingdom | 19 | And the following shipping methods exist: 20 | | zone | name | 21 | | UK | DHL Express | 22 | And the following payment methods exist: 23 | | name | gateway | enabled | 24 | | Dummy | dummy | yes | 25 | 26 | Scenario: Placing the order 27 | Given I am logged in user 28 | And I added product "PHP Top" to cart 29 | And I go to the checkout start page 30 | And I fill in the shipping address to United Kingdom 31 | And I press "Continue" 32 | And I select the "DHL Express" radio button 33 | And I press "Continue" 34 | And I select the "Dummy" radio button 35 | And I press "Continue" 36 | When I click "Place order" 37 | Then I should be on the store homepage 38 | And I should see "Thank you for your order!" 39 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/java/UsedBy.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.java; 2 | 3 | import static tzatziki.util.Equal.areEquals; 4 | 5 | /** 6 | * @author @aloyer 7 | */ 8 | public class UsedBy { 9 | private final String featureUri; 10 | private final String scenarioOutlineName; 11 | private final String scenarioName; 12 | 13 | public UsedBy(String featureUri, String scenarioOutlineName, String scenarioName) { 14 | this.featureUri = featureUri; 15 | this.scenarioOutlineName = scenarioOutlineName; 16 | this.scenarioName = scenarioName; 17 | } 18 | 19 | @Override 20 | public boolean equals(Object o) { 21 | if (this == o) return true; 22 | if (o == null || getClass() != o.getClass()) return false; 23 | 24 | UsedBy usedBy = (UsedBy) o; 25 | return areEquals(featureUri, usedBy.featureUri) 26 | && areEquals(scenarioName, usedBy.scenarioName) 27 | && areEquals(scenarioOutlineName, usedBy.scenarioOutlineName); 28 | } 29 | 30 | @Override 31 | public int hashCode() { 32 | int result = featureUri.hashCode(); 33 | result = 31 * result + (scenarioOutlineName != null ? scenarioOutlineName.hashCode() : 0); 34 | result = 31 * result + scenarioName.hashCode(); 35 | return result; 36 | } 37 | 38 | @Override 39 | public String toString() { 40 | return "UsedBy{" + 41 | "featureUri='" + featureUri + '\'' + 42 | ", scenarioOutlineName='" + scenarioOutlineName + '\'' + 43 | ", scenarioName='" + scenarioName + '\'' + 44 | '}'; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/java/HumanReadableRegex.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.java; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | /** 6 | * @author @aloyer 7 | */ 8 | public class HumanReadableRegex { 9 | private final String rawPattern; 10 | private final Pattern pattern; 11 | 12 | public HumanReadableRegex(String rawPattern) { 13 | this.rawPattern = rawPattern; 14 | this.pattern = Pattern.compile(rawPattern); 15 | } 16 | 17 | public String humanReadable() { 18 | String s = discardStartAndEndModifier(rawPattern); 19 | s = replaceCapturingDigits(s); 20 | s = replaceCapturingDecimal(s); 21 | s = replaceCapturingAnything(s); 22 | s = replaceOptionalCharacterWithParenthesis(s); 23 | return s; 24 | } 25 | 26 | private String replaceOptionalCharacterWithParenthesis(String s) { 27 | return s.replaceAll("([a-z])\\?", "($1)"); 28 | } 29 | 30 | private String replaceCapturingAnything(String s) { 31 | return s.replaceAll("\\((\\[\\^\"\\][+*]|\\.[+*])\\)", ""); 32 | } 33 | 34 | private String replaceCapturingDigits(String s) { 35 | return s.replaceAll("\\(\\\\d[+*]\\)", ""); 36 | } 37 | 38 | private String replaceCapturingDecimal(String s) { 39 | return s.replaceAll("\\(\\\\d[+](\\\\.\\?)?\\|\\\\d[*]\\\\.\\\\d[+]\\)", ""); 40 | } 41 | 42 | 43 | private String discardStartAndEndModifier(String value) { 44 | return value 45 | .replaceAll("^\\^(.*)\\$", "$1") 46 | .replaceAll("^\\^(.*)", "$1") 47 | .replaceAll("(.*)\\$", "$1"); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/game-of-life/game-of-life.feature: -------------------------------------------------------------------------------- 1 | Feature: The game of life should... 2 | 1. Any live cell with fewer than two live neighbours dies, as if caused by underpopulation. 3 | 2. Any live cell with more than three live neighbours dies, as if by overcrowding. 4 | 3. Any live cell with two or three live neighbours lives on to the next generation. 5 | 4. Any dead cell with exactly three live neighbours becomes a live cell. 6 | 7 | @underpopulation 8 | Scenario: Cell has no neighbors 9 | 10 | Given Cell is alive 11 | And Cell has "0" neighbors 12 | When I go to the next generation 13 | Then Cell should be dead 14 | 15 | @underpopulation 16 | Scenario: Cell has one neighbor 17 | 18 | Given Cell is alive 19 | And Cell has "1" neighbors 20 | When I go to the next generation 21 | Then Cell should be dead 22 | 23 | @survive 24 | Scenario: Cell survives to the next generation with two neighbors 25 | 26 | Given Cell is alive 27 | And Cell has "2" neighbors 28 | When I go to the next generation 29 | Then Cell should be alive 30 | 31 | @survive 32 | Scenario: Cell survives to the next generation 33 | 34 | Given Cell is alive 35 | And Cell has "3" neighbors 36 | When I go to the next generation 37 | Then Cell should be alive 38 | 39 | @overcrowding 40 | Scenario: Cell dies of overpopulation 41 | 42 | Given Cell is alive 43 | And Cell has "4" neighbors 44 | When I go to the next generation 45 | Then Cell should be dead 46 | 47 | @generation 48 | Scenario: Empty cell has a birth 49 | 50 | Given Cell is dead 51 | And Cell has "3" neighbors 52 | When I go to the next generation 53 | Then Cell should be alive 54 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/stylius/backend/dashboard.feature: -------------------------------------------------------------------------------- 1 | @dashboard 2 | Feature: Store dashboard 3 | In order to have an overview of my business 4 | As a store owner 5 | I need to be able to see sales info in backend dashboard 6 | 7 | Background: 8 | Given I am logged in as administrator 9 | And the following zones are defined: 10 | | name | type | members | 11 | | German lands | country | Germany, Austria, Switzerland | 12 | And there are products: 13 | | name | price | 14 | | Mug | 5.99 | 15 | | Sticker | 10.00 | 16 | And the following orders were placed: 17 | | user | address | 18 | | klaus@example.com | Klaus Schmitt, Heine-Straße 12, 99734, Berlin, Germany | 19 | | lars@example.com | Lars Meine, Fun-Straße 1, 90032, Vienna, Austria | 20 | And order #000000001 has following items: 21 | | product | quantity | 22 | | Mug | 2 | 23 | And order #000000002 has following items: 24 | | product | quantity | 25 | | Mug | 1 | 26 | | Sticker | 4 | 27 | 28 | Scenario: Viewing the dashboard at website root 29 | Given I am on the dashboard page 30 | Then I should see "Administration dashboard" 31 | 32 | Scenario: Viewing recent orders 33 | Given I am on the dashboard page 34 | Then I should see 2 orders in the list 35 | 36 | Scenario: Viewing recent users 37 | Given I am on the dashboard page 38 | Then I should see 3 users in the list 39 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/exec/gson/JsonIO.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec.gson; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.google.gson.Gson; 5 | import com.google.gson.GsonBuilder; 6 | import tzatziki.analysis.exec.model.FeatureExec; 7 | import tzatziki.analysis.exec.model.ScenarioExec; 8 | import tzatziki.analysis.exec.model.ScenarioOutlineExec; 9 | import tzatziki.analysis.exec.model.StepContainer; 10 | 11 | import java.io.InputStream; 12 | import java.io.InputStreamReader; 13 | import java.io.UnsupportedEncodingException; 14 | import java.util.List; 15 | 16 | /** 17 | * @author @aloyer 18 | */ 19 | public class JsonIO { 20 | 21 | public Gson createGson() { 22 | Gson delegate = new GsonBuilder().setPrettyPrinting().create(); 23 | return new GsonBuilder() 24 | .setPrettyPrinting() 25 | .registerTypeAdapter(ScenarioExec.class, new ScenarioExecSerializer(delegate)) 26 | .registerTypeAdapter(ScenarioOutlineExec.class, new ScenarioOutlineExecSerializer(delegate)) 27 | .registerTypeAdapter(StepContainer.class, new StepContainerDeserializer(delegate)) 28 | .create(); 29 | } 30 | 31 | public List load(InputStream in) throws UnsupportedEncodingException { 32 | return load(in, "UTF8"); 33 | } 34 | 35 | public List load(InputStream in, String charset) throws UnsupportedEncodingException { 36 | Features features = createGson().fromJson(new InputStreamReader(in, charset), Features.class); 37 | return features.features; 38 | } 39 | 40 | public static class Features { 41 | private List features = Lists.newArrayList(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/coffeemachine/01-making-drinks.feature: -------------------------------------------------------------------------------- 1 | Feature: Making Drinks 2 | 3 | # 4 | # **In order** to send commands to the drink maker 5 | # **As a** developer 6 | # **I want to** implement the logic that translates orders from customers of the coffee machine to the drink maker 7 | # 8 | # {width:66px, height:100px} 9 | # ![Coffee cup](${imageDir}/coffee-cup.jpeg) 10 | # 11 | # The code will use the drink maker protocol (see below) to send commands to the drink maker. 12 | # 13 | # The coffee machine can serves 3 type of drinks: 14 | # 15 | # * tea, 16 | # * coffee, 17 | # * chocolate. 18 | # 19 | # 20 | #```ditaa 21 | # 22 | # /---------+ +------------+ 23 | # | Order |---->| Protocol | 24 | # +---------/ +------------+ 25 | # 26 | #``` 27 | 28 | @takeOrder @wip @tea @sugar @protocol 29 | Scenario: A tea with 1 sugar and a stick 30 | 31 | When I order a "Tea" with 1 sugar 32 | Then the instruction generated should be "T:1:0" 33 | 34 | @takeOrder @chocolate @noSugar @protocol 35 | Scenario: A chocolate with no sugar - and therefore no stick 36 | 37 | When I order a "Chocolate" with 0 sugar 38 | Then the instruction generated should be "H::" 39 | 40 | @takeOrder @coffee @sugar @protocol 41 | Scenario: A tea with 1 sugar and a stick 42 | 43 | When I order a "Coffee" with 2 sugar 44 | Then the instruction generated should be "C:2:0" 45 | 46 | @message @protocol 47 | Scenario Outline: any message received is forwarded for the customer to see 48 | When the message "" is sent 49 | Then the instruction generated should be "" 50 | 51 | Examples: 52 | | message | expected | 53 | | Hello | M:Hello | 54 | | Not enough money | M:Not enough money | -------------------------------------------------------------------------------- /tzatziki-web/src/main/java/tzatziki/web/App.java: -------------------------------------------------------------------------------- 1 | package tzatziki.web; 2 | 3 | 4 | import io.dropwizard.Application; 5 | import io.dropwizard.assets.AssetsBundle; 6 | import io.dropwizard.jdbi.DBIFactory; 7 | import io.dropwizard.setup.Bootstrap; 8 | import io.dropwizard.setup.Environment; 9 | import org.skife.jdbi.v2.DBI; 10 | 11 | 12 | /** 13 | * @author @aloyer 14 | */ 15 | public class App extends Application { 16 | private GrammarDAO grammarDAO; 17 | 18 | public static void main(String[] args) throws Exception { 19 | new App().run(args); 20 | } 21 | 22 | @Override 23 | public void initialize(Bootstrap bootstrap) { 24 | bootstrap.addBundle(new AssetsBundle("/assets/", "/")); 25 | } 26 | 27 | @Override 28 | public void run(AppConfiguration config, Environment environment) throws Exception { 29 | DBIFactory factory = new DBIFactory(); 30 | DBI jdbi = factory.build(environment, config.getDataSourceFactory(), "db"); 31 | ScenarioDAO scenarioDAO = jdbi.onDemand(ScenarioDAO.class); 32 | 33 | GrammarResource grammarResource = new GrammarResource(grammarDAO); 34 | GrammarDAOHealthCheck grammarDAOHealthCheck = new GrammarDAOHealthCheck(grammarDAO); 35 | 36 | ScenarioResource scenarioResource = new ScenarioResource(scenarioDAO); 37 | ScenarioDAOHealthCheck scenarioDAOHealthCheck = new ScenarioDAOHealthCheck(scenarioDAO); 38 | 39 | environment.jersey().register(grammarResource); 40 | environment.jersey().register(scenarioResource); 41 | environment.healthChecks().register("grammar-dao", grammarDAOHealthCheck); 42 | environment.healthChecks().register("scenario-dao", scenarioDAOHealthCheck); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/exec/gson/StepContainerDeserializer.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec.gson; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonDeserializationContext; 5 | import com.google.gson.JsonDeserializer; 6 | import com.google.gson.JsonElement; 7 | import com.google.gson.JsonParseException; 8 | import com.google.gson.JsonPrimitive; 9 | import tzatziki.analysis.exec.model.ScenarioExec; 10 | import tzatziki.analysis.exec.model.ScenarioOutlineExec; 11 | import tzatziki.analysis.exec.model.StepContainer; 12 | 13 | import java.lang.reflect.Type; 14 | 15 | /** 16 | * @author @aloyer 17 | */ 18 | public class StepContainerDeserializer implements JsonDeserializer { 19 | public static final String TYPE = "type"; 20 | public static final String SCENARIO = "scenario"; 21 | public static final String SCENARIO_OUTLINE = "scenario-outline"; 22 | 23 | private final Gson delegate; 24 | 25 | public StepContainerDeserializer(Gson delegate) { 26 | this.delegate = delegate; 27 | } 28 | 29 | @Override 30 | public StepContainer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 31 | JsonPrimitive typeAsJson = json.getAsJsonObject().getAsJsonPrimitive(TYPE); 32 | if (typeAsJson != null) { 33 | String type = typeAsJson.getAsString(); 34 | if (type.equals(SCENARIO)) 35 | return delegate.fromJson(json, ScenarioExec.class); 36 | else if (type.equals(SCENARIO_OUTLINE)) 37 | return delegate.fromJson(json, ScenarioOutlineExec.class); 38 | } 39 | 40 | // fallback? 41 | return delegate.fromJson(json, ScenarioExec.class); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/java/ClassEntry.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.java; 2 | 3 | import com.google.common.base.Predicate; 4 | import com.google.common.collect.FluentIterable; 5 | import com.google.common.collect.Lists; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @author @aloyer 11 | */ 12 | public class ClassEntry extends Describable { 13 | private final String packageName; 14 | private final String name; 15 | private final List methodEntries; 16 | 17 | public ClassEntry(String packageName, String name) { 18 | this.packageName = packageName; 19 | this.name = name; 20 | this.methodEntries = Lists.newArrayList(); 21 | } 22 | 23 | public String name() { 24 | return name; 25 | } 26 | 27 | public String packageName() { 28 | return packageName; 29 | } 30 | 31 | public String qualifiedName() { 32 | return packageName() + '.' + name(); 33 | } 34 | 35 | 36 | public void mergeClass(ClassEntry other) { 37 | this.describeWith(other.comment()); 38 | } 39 | 40 | public void declareEntry(MethodEntry methodEntry) { 41 | methodEntries.add(methodEntry); 42 | } 43 | 44 | public boolean hasEntries() { 45 | return !methodEntries.isEmpty(); 46 | } 47 | 48 | public FluentIterable methods() { 49 | return FluentIterable.from(methodEntries); 50 | } 51 | 52 | public FluentIterable matchingEntries(final String text) { 53 | return methods().filter(new Predicate() { 54 | @Override 55 | public boolean apply(MethodEntry methodEntry) { 56 | return methodEntry.matches(text); 57 | } 58 | }); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/stylius/frontend/user_login.feature: -------------------------------------------------------------------------------- 1 | @users 2 | Feature: Sign in to the store 3 | In order to view my orders list 4 | As a visitor 5 | I need to be able to log in to the store 6 | 7 | Background: 8 | Given there are following users: 9 | | email | password | enabled | 10 | | bar@foo.com | foo | yes | 11 | 12 | Scenario: Log in with username and password 13 | Given I am on the store homepage 14 | And I follow "Login" 15 | When I fill in the following: 16 | | Email | bar@foo.com | 17 | | Password | foo | 18 | And I press "Login" 19 | Then I should be on the store homepage 20 | And I should see "Logout" 21 | 22 | Scenario: Log in with bad credentials 23 | Given I am on the store homepage 24 | And I follow "Login" 25 | When I fill in the following: 26 | | Email | bar@foo.com | 27 | | Password | bar | 28 | And I press "Login" 29 | Then I should be on login page 30 | And I should see "Bad credentials" 31 | 32 | Scenario: Trying to login without credentials 33 | Given I am on the store homepage 34 | And I follow "Login" 35 | When I press "Login" 36 | Then I should be on login page 37 | And I should see "Bad credentials" 38 | 39 | Scenario: Trying to login as non existing user 40 | Given I am on the store homepage 41 | And I follow "Login" 42 | When I fill in the following: 43 | | Email | john | 44 | | Password | bar | 45 | And I press "Login" 46 | Then I should be on login page 47 | And I should see "Bad credentials" 48 | -------------------------------------------------------------------------------- /tzatziki-samples/src/test/java/samples/coffeemachine/CoffeeMachineDrinkTagCheckTest.java: -------------------------------------------------------------------------------- 1 | package samples.coffeemachine; 2 | 3 | import static tzatziki.junit.SanityTagChecker.loadFeaturesFromSourceDirectory; 4 | 5 | import java.io.File; 6 | import java.util.EnumSet; 7 | import java.util.Set; 8 | 9 | import org.junit.runner.RunWith; 10 | 11 | import samples.TestSettings; 12 | import tzatziki.analysis.check.CheckAtLeastOneTagsExist; 13 | import tzatziki.analysis.check.CucumberPart; 14 | import tzatziki.analysis.check.TagChecker; 15 | import tzatziki.analysis.step.Features; 16 | import tzatziki.analysis.tag.TagDictionary; 17 | import tzatziki.junit.SanityTagChecker; 18 | 19 | /** 20 | * @author @aloyer 21 | */ 22 | @RunWith(SanityTagChecker.class) 23 | public class CoffeeMachineDrinkTagCheckTest { 24 | 25 | @SanityTagChecker.TagDictionaryProvider 26 | public static TagDictionary tagDictionary() { 27 | return new TagDictionary() 28 | .declareTag("@coffee") 29 | .declareTag("@tea") 30 | .declareTag("@chocolate") 31 | .declareTag("@orangeJuice") 32 | .declareTag("@noDrink") 33 | ; 34 | } 35 | 36 | @SanityTagChecker.FeaturesProvider 37 | public static Features features() { 38 | String basedir = new TestSettings().getBaseDir(); 39 | return loadFeaturesFromSourceDirectory(new File(basedir, "src/main/resources/samples/coffeemachine")); 40 | } 41 | 42 | @SanityTagChecker.TagCheckerProvider 43 | public static TagChecker checker() { 44 | return new CheckAtLeastOneTagsExist(); 45 | } 46 | 47 | @SanityTagChecker.CheckScopeProvider 48 | public static Set scope() { 49 | return EnumSet.of(CucumberPart.Scenario); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/java/ConsoleOutputListener.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.java; 2 | 3 | import com.thoughtworks.qdox.model.JavaClass; 4 | import com.thoughtworks.qdox.model.JavaMethod; 5 | import com.thoughtworks.qdox.model.JavaPackage; 6 | 7 | import java.util.Collection; 8 | 9 | /** 10 | * @author @aloyer 11 | */ 12 | public class ConsoleOutputListener implements GrammarParserListener { 13 | @Override 14 | public void aboutToParsePackages(Collection packages) { 15 | System.out.println("ConsoleOutputListener.aboutToParsePackages: " + packages); 16 | } 17 | 18 | @Override 19 | public void aboutToParseClasses(Collection classes) { 20 | System.out.println("ConsoleOutputListener.aboutToParseClasses: " + classes); 21 | } 22 | 23 | @Override 24 | public void enteringPackage(JavaPackage pkg) { 25 | System.out.println("ConsoleOutputListener.enteringPackage: " + pkg); 26 | } 27 | 28 | @Override 29 | public void exitingPackage(JavaPackage pkg) { 30 | System.out.println("ConsoleOutputListener.exitingPackage: " + pkg); 31 | } 32 | 33 | @Override 34 | public void enteringClass(JavaClass klazz) { 35 | System.out.println("ConsoleOutputListener.enteringClass: " + klazz); 36 | } 37 | 38 | @Override 39 | public void exitingClass(JavaClass klazz) { 40 | System.out.println("ConsoleOutputListener.exitingClass: " + klazz); 41 | } 42 | 43 | @Override 44 | public void enteringMethod(JavaMethod method) { 45 | System.out.println("ConsoleOutputListener.enteringMethod: " + method); 46 | } 47 | 48 | @Override 49 | public void exitingMethod(JavaMethod method) { 50 | System.out.println("ConsoleOutputListener.exitingMethod: " + method); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tzatziki-core/src/test/resources/tzatziki/junit/coffeemachine/01-making-drinks.feature: -------------------------------------------------------------------------------- 1 | Feature: Making Drinks 2 | 3 | # 4 | # **In order** to send commands to the drink maker 5 | # 6 | # **As a** developer 7 | # 8 | # **I want to** implement the logic that translates orders 9 | # from customers of the coffee machine to the drink maker 10 | # 11 | #

12 | # 13 | #

14 | # 15 | #The code will use the drink maker protocol (see below) to send commands to the drink maker. 16 | # 17 | #The coffee machine can serves 3 type of drinks: 18 | # 19 | # * tea, 20 | # * coffee, 21 | # * chocolate. 22 | # 23 | # 24 | #[ asciidiag ] 25 | #---- 26 | # 27 | # /---------+ +------------+ 28 | # | Order |---->| Protocol | 29 | # +---------/ +------------+ 30 | # 31 | #---- 32 | 33 | @takeOrder @wip @tea @sugar @protocol 34 | Scenario: A tea with 1 sugar and a stick 35 | 36 | When I order a "Tea" with 1 sugar 37 | Then the instruction generated should be "T:1:0" 38 | 39 | @takeOrder @chocolate @noSugar @protocol 40 | Scenario: A chocolate with no sugar - and therefore no stick 41 | 42 | When I order a "Chocolate" with 0 sugar 43 | Then the instruction generated should be "H::" 44 | 45 | @takeOrder @coffee @sugar @protocol 46 | Scenario: A tea with 1 sugar and a stick 47 | 48 | When I order a "Coffee" with 2 sugar 49 | Then the instruction generated should be "C:2:0" 50 | 51 | @message @protocol 52 | Scenario Outline: any message received is forwarded for the customer to see 53 | When the message "" is sent 54 | Then the instruction generated should be "" 55 | 56 | Examples: 57 | | message | expected | 58 | | Hello | M:Hello | 59 | | Not enough money | M:Not enough money | -------------------------------------------------------------------------------- /tzatziki-core/src/test/java/tzatziki/analysis/exec/gson/JsonIOTest.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec.gson; 2 | 3 | import com.google.gson.Gson; 4 | import junitparams.JUnitParamsRunner; 5 | import junitparams.Parameters; 6 | import org.apache.commons.io.IOUtils; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.skyscreamer.jsonassert.JSONAssert; 11 | import tzatziki.analysis.exec.model.FeatureExec; 12 | 13 | import java.io.InputStream; 14 | import java.util.List; 15 | 16 | @RunWith(JUnitParamsRunner.class) 17 | public class JsonIOTest { 18 | 19 | private JsonIO jsonIO; 20 | private Gson gson; 21 | 22 | @Before 23 | public void setUp() { 24 | jsonIO = new JsonIO(); 25 | gson = jsonIO.createGson(); 26 | } 27 | 28 | @Test 29 | @Parameters({ 30 | "/tzatziki/analysis/exec/gson/coffeemachine-exec.json"}) 31 | public void readAndWrite_should_lead_to_identity(String resource) throws Exception { 32 | InputStream in = getClass().getResourceAsStream(resource); 33 | String origin = IOUtils.toString(in, "UTF8"); 34 | 35 | in = getClass().getResourceAsStream(resource); 36 | List featureExecs = jsonIO.load(in); 37 | 38 | String json = toJson(gson, featureExecs).toString(); 39 | JSONAssert.assertEquals(origin, json, false); 40 | } 41 | 42 | private static StringBuilder toJson(Gson gson, List featureExecs) { 43 | StringBuilder json = new StringBuilder(); 44 | json.append("{\"features\": [\n"); 45 | for (int i = 0; i < featureExecs.size(); i++) { 46 | if (i > 0) 47 | json.append(",\n"); 48 | FeatureExec f = featureExecs.get(i); 49 | json.append(gson.toJson(f)); 50 | } 51 | json.append("]\n}"); 52 | return json; 53 | } 54 | } -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/stylius/frontend/checkout_addressing.feature: -------------------------------------------------------------------------------- 1 | @checkout 2 | Feature: Checkout addressing 3 | In order to select billing and shipping addresses 4 | As a visitor 5 | I want to proceed through addressing checkout step 6 | 7 | Background: 8 | Given there are following taxonomies defined: 9 | | name | 10 | | Category | 11 | And taxonomy "Category" has following taxons: 12 | | Clothing > PHP T-Shirts | 13 | And the following products exist: 14 | | name | price | taxons | 15 | | PHP Top | 5.99 | PHP T-Shirts | 16 | And there are following users: 17 | | email | password | enabled | 18 | | john@example.com | foo | yes | 19 | | rick@example.com | bar | yes | 20 | And I am logged in user 21 | And there are following countries: 22 | | name | 23 | | USA | 24 | | United Kingdom | 25 | | Poland | 26 | | Germany | 27 | 28 | Scenario: Filling the shipping address 29 | Given I added product "PHP Top" to cart 30 | When I go to the checkout start page 31 | And I fill in the shipping address to United Kingdom 32 | And I press "Continue" 33 | Then I should be on the checkout shipping step 34 | 35 | Scenario: Using different billing address 36 | Given I added product "PHP Top" to cart 37 | When I go to the checkout start page 38 | And I fill in the shipping address to Germany 39 | But I check "Use different address for billing?" 40 | And I fill in the billing address to USA 41 | And I press "Continue" 42 | Then I should be on the checkout shipping step 43 | -------------------------------------------------------------------------------- /tzatziki-core/src/test/java/tzatziki/util/MemoizableIteratorTest.java: -------------------------------------------------------------------------------- 1 | package tzatziki.util; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.Arrays; 6 | import java.util.List; 7 | import java.util.NoSuchElementException; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | public class MemoizableIteratorTest { 12 | 13 | @Test 14 | public void usecase () { 15 | List elements = Arrays.asList("one", "two", "three"); 16 | MemoizableIterator it = new MemoizableIterator(elements.iterator()); 17 | assertThat(it.hasNext()).isTrue(); 18 | assertThat(it.next()).isEqualTo("one"); 19 | assertThat(it.current()).isEqualTo("one"); 20 | 21 | assertThat(it.hasNext()).isTrue(); 22 | assertThat(it.next()).isEqualTo("two"); 23 | assertThat(it.current()).isEqualTo("two"); 24 | assertThat(it.current()).isEqualTo("two"); 25 | 26 | assertThat(it.hasNext()).isTrue(); 27 | assertThat(it.next()).isEqualTo("three"); 28 | assertThat(it.current()).isEqualTo("three"); 29 | assertThat(it.current()).isEqualTo("three"); 30 | 31 | assertThat(it.hasNext()).isFalse(); 32 | assertThat(it.current()).isEqualTo("three"); 33 | } 34 | 35 | @Test(expected = NoSuchElementException.class) 36 | public void next_should_throw_when_no_more_element__initial_empty_case () { 37 | List elements = Arrays.asList(); 38 | MemoizableIterator it = new MemoizableIterator(elements.iterator()); 39 | it.next(); 40 | } 41 | 42 | @Test(expected = IllegalStateException.class) 43 | public void current_should_throw__when_no_more_elements () { 44 | List elements = Arrays.asList(); 45 | MemoizableIterator it = new MemoizableIterator(elements.iterator()); 46 | it.current(); 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /tzatziki-pdf/src/main/java/tzatziki/pdf/emitter/StatusMarker.java: -------------------------------------------------------------------------------- 1 | package tzatziki.pdf.emitter; 2 | 3 | import com.itextpdf.text.BaseColor; 4 | import com.itextpdf.text.Chunk; 5 | import com.itextpdf.text.DocumentException; 6 | import gutenberg.itext.FontAwesomeAdapter; 7 | import tzatziki.analysis.exec.model.Status; 8 | 9 | import java.io.IOException; 10 | 11 | /** 12 | * @author @aloyer 13 | */ 14 | public class StatusMarker { 15 | private FontAwesomeAdapter fontAwesomeAdapter; 16 | private float symbolSize = 12; 17 | 18 | public Chunk statusMarker(Status status) { 19 | switch (status) { 20 | case Passed: 21 | return fontAwesomeAdapter().symbol("check-circle", symbolSize, BaseColor.GREEN.darker()); 22 | case Skipped: 23 | return fontAwesomeAdapter().symbol("exclamation-circle", symbolSize, BaseColor.ORANGE); 24 | case Undefined: 25 | return fontAwesomeAdapter().symbol("question-circle", symbolSize, BaseColor.RED.darker()); 26 | case Failed: 27 | return fontAwesomeAdapter().symbol("ban", symbolSize, BaseColor.RED); 28 | case Pending: 29 | return fontAwesomeAdapter().symbol("gears", symbolSize, BaseColor.ORANGE); 30 | default: 31 | return fontAwesomeAdapter().symbol("minus-circle", symbolSize, BaseColor.BLUE); 32 | } 33 | } 34 | 35 | private FontAwesomeAdapter fontAwesomeAdapter() { 36 | if (fontAwesomeAdapter == null) 37 | try { 38 | fontAwesomeAdapter = new FontAwesomeAdapter(); 39 | } catch (IOException e) { 40 | throw new RuntimeException(e); 41 | } catch (DocumentException e) { 42 | throw new RuntimeException(e); 43 | } 44 | return fontAwesomeAdapter; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/exec/tag/TagFilter.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec.tag; 2 | 3 | import com.google.common.base.Function; 4 | import com.google.common.base.Predicate; 5 | import com.google.common.base.Predicates; 6 | import com.google.common.collect.FluentIterable; 7 | 8 | import static java.util.Arrays.asList; 9 | 10 | /** 11 | * @author @aloyer 12 | */ 13 | public class TagFilter implements Predicate { 14 | 15 | public static TagFilter AcceptAll = new TagFilter(Predicates.alwaysTrue()); 16 | 17 | public static TagFilter from(String... tagExprs) { 18 | return new TagFilter(new TagExpressionPredicate(asList(tagExprs))); 19 | } 20 | 21 | private final Predicate delegate; 22 | 23 | private TagFilter(Predicate delegate) { 24 | this.delegate = delegate; 25 | } 26 | 27 | @Override 28 | public boolean apply(Tags tags) { 29 | return delegate.apply(tags); 30 | } 31 | 32 | public TagFilter and(TagFilter... tagFilters) { 33 | Iterable> ands = extractPredicates(tagFilters); 34 | return new TagFilter(Predicates.and(ands)); 35 | } 36 | 37 | public TagFilter or(TagFilter... tagFilters) { 38 | Iterable> ors = extractPredicates(tagFilters); 39 | return new TagFilter(Predicates.or(ors)); 40 | } 41 | 42 | private static FluentIterable> extractPredicates(TagFilter[] tagFilters) { 43 | return FluentIterable.from(asList(tagFilters)).transform(tagFilterPredicateLens); 44 | } 45 | 46 | private static final Function> tagFilterPredicateLens = new Function>() { 47 | @Override 48 | public Predicate apply(TagFilter filter) { 49 | return filter.delegate; 50 | } 51 | }; 52 | 53 | } 54 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/stylius/backend/products_filter.feature: -------------------------------------------------------------------------------- 1 | @products 2 | Feature: Products filter 3 | In order to easily find products 4 | As a store owner 5 | I want to be able to filter list by name 6 | 7 | Background: 8 | Given I am logged in as administrator 9 | And the following products exist: 10 | | name | price | sku | 11 | | Super T-Shirt | 19.99 | 123 | 12 | | Black T-Shirt | 19.99 | 321 | 13 | | Mug | 5.99 | 136 | 14 | | Sticker | 10.00 | 555 | 15 | | Banana | 10.00 | 999 | 16 | | Orange | 10.00 | 124 | 17 | 18 | Scenario: Filtering products by name 19 | Given I am on the product index page 20 | When I fill in "Name" with "T-Shirt" 21 | And I press "Filter" 22 | Then I should be on the product index page 23 | And I should see 2 products in the list 24 | 25 | Scenario: Not matching products are not in the list 26 | Given I am on the product index page 27 | When I fill in "Name" with "T-Shirt" 28 | And I press "Filter" 29 | Then I should be on the product index page 30 | And I should not see "Orange" 31 | But I should see "Black T-Shirt" 32 | 33 | Scenario: Filtering products by SKU 34 | Given I am on the product index page 35 | When I fill in "SKU" with "123" 36 | And I press "Filter" 37 | Then I should be on the product index page 38 | And I should see 1 product in the list 39 | 40 | Scenario: Products with not matching SKU are filtered 41 | Given I am on the product index page 42 | When I fill in "SKU" with "555" 43 | And I press "Filter" 44 | Then I should be on the product index page 45 | And I should see "Sticker" 46 | But I should not see "T-Shirt" 47 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/stylius/frontend/account_password.feature: -------------------------------------------------------------------------------- 1 | @account 2 | Feature: User account password change 3 | In order to enhance the security of my account 4 | As a logged user 5 | I want to be able to change password 6 | 7 | Background: 8 | Given I am logged in user 9 | And I am on my account homepage 10 | 11 | Scenario: Viewing my password change page 12 | Given I follow "My password" 13 | Then I should be on my account password page 14 | 15 | Scenario: Changing my password with a wrong current password 16 | Given I am on my account password page 17 | When I fill in "Current password" with "wrongpassword" 18 | And I fill in "New password" with "newpassword" 19 | And I fill in "Confirmation" with "newpassword" 20 | And I press "Save changes" 21 | Then I should still be on my account password page 22 | And I should see "This value should be the user current password" 23 | 24 | Scenario: Changing my password with a wrong confirmation password 25 | Given I am on my account password page 26 | When I fill in "Current password" with "sylius" 27 | And I fill in "New password" with "newpassword" 28 | And I fill in "Confirmation" with "wrongnewpassword" 29 | And I press "Save changes" 30 | Then I should still be on my account password page 31 | And I should see "The entered passwords don't match" 32 | 33 | Scenario: Successfully changing my password 34 | Given I am on my account password page 35 | When I fill in "Current password" with "sylius" 36 | And I fill in "New password" with "newpassword" 37 | And I fill in "Confirmation" with "newpassword" 38 | And I press "Save changes" 39 | Then I should be on my account profile page 40 | And I should see "The password has been changed" 41 | 42 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/stylius/frontend/checkout_taxation.feature: -------------------------------------------------------------------------------- 1 | @checkout 2 | Feature: Checkout taxation 3 | In order to handle product taxation 4 | As a store owner 5 | I want to apply taxes during checkout 6 | 7 | Background: 8 | Given there are following taxonomies defined: 9 | | name | 10 | | Category | 11 | And taxonomy "Category" has following taxons: 12 | | Clothing > PHP T-Shirts | 13 | And the following zones are defined: 14 | | name | type | members | 15 | | UK | country | United Kingdom | 16 | And there are following tax categories: 17 | | name | 18 | | Taxable Goods | 19 | And the following tax rates exist: 20 | | category | zone | name | amount | 21 | | Taxable Goods | UK | UK Tax | 15% | 22 | And the following products exist: 23 | | name | price | taxons | tax category | 24 | | PHP Top | 250 | PHP T-Shirts | Taxable Goods | 25 | And the following shipping methods exist: 26 | | zone | name | 27 | | UK | DHL Express | 28 | And the following payment methods exist: 29 | | name | gateway | enabled | 30 | | Dummy | dummy | yes | 31 | And I am logged in user 32 | And I added product "PHP Top" to cart 33 | And I go to the checkout start page 34 | 35 | Scenario: Placing the order 36 | Given I fill in the shipping address to United Kingdom 37 | And I press "Continue" 38 | And I select the "DHL Express" radio button 39 | And I press "Continue" 40 | And I select the "Dummy" radio button 41 | When I press "Continue" 42 | Then I should be on the checkout finalize step 43 | And "Tax total: €37.50" should appear on the page 44 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/coffeemachine/04-making-money.feature: -------------------------------------------------------------------------------- 1 | Feature: Making Money 2 | 3 | # 4 | # **In order to** have daily reports of what is sold and when 5 | # 6 | # **As a** shop keeper 7 | # 8 | # **I want to** track statistics of machine usage 9 | # 10 | #```formula 11 | # {\eta \leq C(\delta(\eta) +\Lambda_M(0,\delta)) 12 | #``` 13 | # 14 | #```formula 15 | # \Re{z} =\frac{n\pi \dfrac{\theta +\psi}{2}}{ 16 | # \left(\dfrac{\theta +\psi}{2}\right)^2 + \left( \dfrac{1}{2} 17 | # \log \left\vert\dfrac{B}{A}\right\vert\right)^2}. 18 | #``` 19 | # 20 | 21 | @reporting @tea @coffee @chocolate 22 | Scenario: Statistics collect basic usage 23 | 24 | Given the following orders: 25 | | time | drink | 26 | | 08:05:23 | Coffee | 27 | | 08:06:43 | Coffee | 28 | | 08:10:23 | Coffee | 29 | | 08:45:03 | Tea | 30 | | 10:05:47 | Coffee | 31 | | 10:05:47 | Chocolate | 32 | When I query for a report 33 | Then the report output should be 34 | """ 35 | chocolate: 1 36 | coffee: 4 37 | tea: 1 38 | --- 39 | Total: 3.00€ 40 | """ 41 | 42 | @reporting @noDrink 43 | Scenario: Statistics collect no usage 44 | 45 | #```ditaa 46 | # 47 | # /---------------+-------------\ 48 | # |cRED Chocolate |cBLU 1 |-----\ 49 | # +---------------+-------------+ : 50 | # |cGRE Coffee |cPNK 4 | | 51 | # +---------------+-------------+ | 52 | # |cAAA Total |<----/ 53 | # +-----------------------------+ /-------\ 54 | # |cCCC 3.00€ |---+---->| DB | 55 | # | +-------------+ | {s}| 56 | # | |cYEL YEL | \-------/ 57 | # \---------------+-------------/ 58 | # 59 | #``` 60 | 61 | When I query for a report 62 | Then the report output should be 63 | """ 64 | --- 65 | Total: 0.00€ 66 | """ -------------------------------------------------------------------------------- /tzatziki-core/src/test/resources/tzatziki/analysis/step/going-into-business.feature: -------------------------------------------------------------------------------- 1 | Feature: Going into business 2 | 3 | # 4 | # **In order to** goes into Business 5 | # 6 | # **As a** shopkeeper 7 | # 8 | # **I want to** ensure The coffee machine is not free anymore! 9 | # 10 | #

11 | # 12 | #

13 | # 14 | 15 | @Payment 16 | Scenario: A tea with just enough money 17 | 18 | The drink maker should make the drinks only if the correct amount of money is given 19 | 20 | Given I've inserted 0.40€ in the machine 21 | When I order a "Tea" with 1 sugar 22 | Then the instruction generated should be "T:1:0" 23 | 24 | @Payment 25 | Scenario: A tea with not enough money 26 | 27 | If not enough money is provided, we want to send a message to the drink maker. 28 | The message should contains at least the amount of money missing. 29 | 30 | Given I've inserted 0.30€ in the machine 31 | When I order a "Tea" with 1 sugar 32 | Then the instruction generated should be "M:Not enough money 0.10 missing" 33 | 34 | @Payment 35 | Scenario: A coffee with more than required money 36 | 37 | If too much money is given, the drink maker will still make the drink according 38 | to the instructions. The machine will handle the return of the correct change. 39 | 40 | Given I've inserted 2€ in the machine 41 | When I order a "Coffee" with 0 sugar 42 | Then the instruction generated should be "C::" 43 | 44 | @Payment 45 | Scenario Outline: Check missing money 46 | 47 | Given I've inserted € in the machine 48 | When I order a "" with sugar 49 | Then the instruction generated should be "" 50 | 51 | Examples: 52 | | money | drink | n | instruction | 53 | | 0.25 | Coffee | 0 | M:Not enough money 0.25 missing | 54 | | 0.55 | Chocolate | 0 | M:Not enough money 0.05 missing | 55 | | 0.05 | Tea | 1 | M:Not enough money 0.35 missing | 56 | 57 | -------------------------------------------------------------------------------- /tzatziki-pdf/src/main/java/tzatziki/pdf/emitter/ScenarioEmitter.java: -------------------------------------------------------------------------------- 1 | package tzatziki.pdf.emitter; 2 | 3 | import gutenberg.itext.Emitter; 4 | import gutenberg.itext.ITextContext; 5 | import gutenberg.itext.Sections; 6 | import gutenberg.util.KeyValues; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import tzatziki.analysis.exec.model.ScenarioExec; 10 | 11 | /** 12 | * @author @aloyer 13 | */ 14 | public class ScenarioEmitter implements Emitter { 15 | 16 | public static final String DISPLAY_TAGS = "scenario-display-tags"; 17 | private final int hLevel; 18 | private StepContainerEmitter stepsEmitter; 19 | 20 | private Logger log = LoggerFactory.getLogger(ScenarioEmitter.class); 21 | 22 | public ScenarioEmitter() { 23 | this(2); 24 | } 25 | 26 | public ScenarioEmitter(int hLevel) { 27 | this(hLevel, new StepContainerEmitter()); 28 | } 29 | 30 | public ScenarioEmitter(int hLevel, StepContainerEmitter stepsEmitter) { 31 | this.hLevel = hLevel; 32 | this.stepsEmitter = stepsEmitter; 33 | } 34 | 35 | @Override 36 | public void emit(ScenarioExec scenario, ITextContext emitterContext) { 37 | Sections sections = emitterContext.sections(); 38 | KeyValues kvs = emitterContext.keyValues(); 39 | 40 | Integer rawOffset = kvs.getInteger(FeatureEmitter.FEATURE_HEADER_LEVEL_OFFSET).or(0); 41 | int headerLevel = hLevel + rawOffset; 42 | 43 | sections.newSection(scenario.name(), headerLevel); 44 | try { 45 | if (kvs.getBoolean(DISPLAY_TAGS, true)) { 46 | stepsEmitter.emitTags(scenario, emitterContext); 47 | } 48 | stepsEmitter.emitDescription(scenario, emitterContext); 49 | stepsEmitter.emitEmbeddings(scenario, emitterContext); 50 | stepsEmitter.emitSteps(scenario, emitterContext); 51 | } finally { 52 | sections.leaveSection(headerLevel); // end-of-scenario 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tzatziki-core/src/test/resources/tzatziki/junit/coffeemachine/04-making-money.feature: -------------------------------------------------------------------------------- 1 | Feature: Making Money 2 | 3 | # 4 | # **In order to** have daily reports of what is sold and when 5 | # 6 | # **As a** shop keeper 7 | # 8 | # **I want to** track statistics of machine usage 9 | # 10 | # [formula] 11 | # ---- 12 | # {\eta \leq C(\delta(\eta) +\Lambda_M(0,\delta)) 13 | # ---- 14 | # 15 | # {% formula %} 16 | # 17 | # \Re{z} =\frac{n\pi \dfrac{\theta +\psi}{2}}{ 18 | # \left(\dfrac{\theta +\psi}{2}\right)^2 + \left( \dfrac{1}{2} 19 | # \log \left\vert\dfrac{B}{A}\right\vert\right)^2}. 20 | # 21 | # {% formula %} 22 | # 23 | # 24 | 25 | @reporting @tea @coffee @chocolate 26 | Scenario: Statistics collect basic usage 27 | 28 | Given the following orders: 29 | | time | drink | 30 | | 08:05:23 | Coffee | 31 | | 08:06:43 | Coffee | 32 | | 08:10:23 | Coffee | 33 | | 08:45:03 | Tea | 34 | | 10:05:47 | Coffee | 35 | | 10:05:47 | Chocolate | 36 | When I query for a report 37 | Then the report output should be 38 | """ 39 | chocolate: 1 40 | coffee: 4 41 | tea: 1 42 | --- 43 | Total: 3.00€ 44 | """ 45 | 46 | @reporting @noDrink 47 | Scenario: Statistics collect no usage 48 | 49 | # [asciidiag] 50 | # ---- 51 | # /---------------+-------------\ 52 | # |cRED Chocolate |cBLU 1 |-----\ 53 | # +---------------+-------------+ : 54 | # |cGRE Coffee |cPNK 4 | | 55 | # +---------------+-------------+ | 56 | # |cAAA Total |<----/ 57 | # +-----------------------------+ /-------\ 58 | # |cCCC 3.00€ |---+---->| DB | 59 | # | +-------------+ | {s}| 60 | # | |cYEL YEL | \-------/ 61 | # \---------------+-------------/ 62 | # ---- 63 | # 64 | 65 | When I query for a report 66 | Then the report output should be 67 | """ 68 | --- 69 | Total: 0.00€ 70 | """ -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/step/TagCollector.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.step; 2 | 3 | import com.google.common.collect.Maps; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | * @author @aloyer 9 | */ 10 | public class TagCollector extends FeatureVisitorAdapter { 11 | 12 | private Map allTags = Maps.newConcurrentMap(); 13 | private Feature feature; 14 | 15 | @Override 16 | public void enterFeature(Feature feature) { 17 | this.feature = feature; 18 | for(String tag : feature.getTags()) { 19 | Stats stats = statsFor(tag); 20 | stats.featureTagged++; 21 | } 22 | } 23 | 24 | @Override 25 | public void enterScenario(Scenario scenario) { 26 | for(String tag : scenario.getTags()) { 27 | Stats stats = statsFor(tag); 28 | stats.scenarioTagged++; 29 | } 30 | for(String tag : feature.getTags()) { 31 | Stats stats = statsFor(tag); 32 | stats.scenarioTaggedByInheritance++; 33 | } 34 | } 35 | 36 | @Override 37 | public void enterScenarioOutline(ScenarioOutline scenario) { 38 | for(String tag : scenario.getTags()) { 39 | Stats stats = statsFor(tag); 40 | stats.scenarioOutlineTagged++; 41 | } 42 | for(String tag : feature.getTags()) { 43 | Stats stats = statsFor(tag); 44 | stats.scenarioOutlineTaggedByInheritance++; 45 | } 46 | } 47 | 48 | private Stats statsFor(String tag) { 49 | Stats stats = allTags.get(tag); 50 | if(stats == null) { 51 | stats = new Stats(); 52 | allTags.put(tag, stats); 53 | } 54 | return stats; 55 | } 56 | 57 | public static class Stats { 58 | public int featureTagged = 0; 59 | public int scenarioTagged = 0; 60 | public int scenarioTaggedByInheritance = 0; 61 | public int scenarioOutlineTagged = 0; 62 | public int scenarioOutlineTaggedByInheritance = 0; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/stylius/frontend/checkout_payment.feature: -------------------------------------------------------------------------------- 1 | @checkout 2 | Feature: Checkout Payment 3 | In order to submit a payment 4 | As a logged in user 5 | I want to be able to use checkout payment step 6 | 7 | Background: 8 | Given there are following taxonomies defined: 9 | | name | 10 | | Category | 11 | And taxonomy "Category" has following taxons: 12 | | Clothing > PHP T-Shirts | 13 | And the following products exist: 14 | | name | price | taxons | 15 | | PHP Top | 5.99 | PHP T-Shirts | 16 | And the following zones are defined: 17 | | name | type | members | 18 | | UK | country | United Kingdom | 19 | And the following shipping methods exist: 20 | | zone | name | 21 | | UK | DHL Express | 22 | And the following payment methods exist: 23 | | name | gateway | enabled | 24 | | Credit Card | stripe | yes | 25 | | PayPal | paypal | yes | 26 | | PayPal PRO | paypal_pro | no | 27 | And I am logged in user 28 | And I added product "PHP Top" to cart 29 | And I go to the checkout start page 30 | And I fill in the shipping address to United Kingdom 31 | And I press "Continue" 32 | And I select the "DHL Express" radio button 33 | 34 | Scenario: Accessing payment checkout step 35 | Given I press "Continue" 36 | Then I should be on the checkout payment step 37 | 38 | Scenario: Only enabled payment methods are displayed 39 | Given I press "Continue" 40 | Then I should be on the checkout payment step 41 | And I should see "PayPal" 42 | But I should not see "PayPal PRO" 43 | 44 | Scenario: Selecting one of payment methods 45 | Given I press "Continue" 46 | When I select the "PayPal" radio button 47 | And I press "Continue" 48 | Then I should be on the checkout finalize step 49 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/step/Feature.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.step; 2 | 3 | import com.google.common.collect.FluentIterable; 4 | import com.google.common.collect.Lists; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author @aloyer 10 | */ 11 | public class Feature { 12 | 13 | private final String uri; 14 | private final String name; 15 | private List tags = Lists.newArrayList(); 16 | private Background background; 17 | private List scenarios = Lists.newArrayList(); 18 | private List scenarioOutlines = Lists.newArrayList(); 19 | 20 | public Feature(String uri, String name) { 21 | this.uri = uri; 22 | this.name = name; 23 | } 24 | 25 | public Background background() { 26 | return background; 27 | } 28 | 29 | public void background(Background background) { 30 | this.background = background; 31 | } 32 | 33 | public void add(Scenario scenario) { 34 | scenarios.add(scenario); 35 | } 36 | 37 | public void add(ScenarioOutline scenarioOutline) { 38 | scenarioOutlines.add(scenarioOutline); 39 | } 40 | 41 | public void traverse(FeatureVisitor visitor) { 42 | visitor.enterFeature(this); 43 | for (Scenario scenario : scenarios) 44 | scenario.traverse(visitor); 45 | for (ScenarioOutline scenarioOutline : scenarioOutlines) 46 | scenarioOutline.traverse(visitor); 47 | visitor.exitFeature(this); 48 | } 49 | 50 | public String uri() { 51 | return uri; 52 | } 53 | 54 | public String name() { 55 | return name; 56 | } 57 | 58 | public void addTags(List tags) { 59 | this.tags.addAll(tags); 60 | } 61 | 62 | public List getTags() { 63 | return tags; 64 | } 65 | 66 | public FluentIterable scenario() { 67 | return FluentIterable.from(scenarios); 68 | } 69 | 70 | public FluentIterable scenarioOutlines() { 71 | return FluentIterable.from(scenarioOutlines); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/stylius/frontend/products.feature: -------------------------------------------------------------------------------- 1 | @products 2 | Feature: Products 3 | In order to know and pick the products 4 | As a visitor 5 | I want to be able to browse products 6 | 7 | Background: 8 | Given there are following taxonomies defined: 9 | | name | 10 | | Category | 11 | And taxonomy "Category" has following taxons: 12 | | Clothing > T-Shirts | 13 | | Clothing > PHP T-Shirts | 14 | | Clothing > Gloves | 15 | And the following products exist: 16 | | name | price | taxons | 17 | | Super T-Shirt | 19.99 | T-Shirts | 18 | | Black T-Shirt | 18.99 | T-Shirts | 19 | | Sylius Tee | 12.99 | PHP T-Shirts | 20 | | Symfony T-Shirt | 15.00 | PHP T-Shirts | 21 | | Doctrine T-Shirt | 15.00 | PHP T-Shirts | 22 | 23 | Scenario: Browsing products by taxon 24 | Given I am on the store homepage 25 | When I follow "T-Shirts" 26 | Then I should see there 2 products 27 | And I should see "Black T-Shirt" 28 | 29 | Scenario: Browsing products by taxon 30 | Given I am on the store homepage 31 | When I follow "PHP T-Shirts" 32 | Then I should see there 3 products 33 | And I should see "Sylius Tee" 34 | 35 | Scenario: Empty index of products 36 | Given there are no products 37 | And I am on the store homepage 38 | When I follow "Gloves" 39 | Then I should see "There are no products to display" 40 | 41 | Scenario: Accessing product page via "View more" button 42 | Given I am on the store homepage 43 | And I follow "T-Shirts" 44 | When I click "View more" 45 | Then I should be on the product page for "Super T-Shirt" 46 | 47 | Scenario: Accessing product page via title 48 | Given I am on the store homepage 49 | And I follow "PHP T-Shirts" 50 | When I click "Symfony T-Shirt" 51 | Then I should be on the product page for "Symfony T-Shirt" 52 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/stylius/frontend/cart_promotions_dates.feature: -------------------------------------------------------------------------------- 1 | @promotions 2 | Feature: Checkout limited time promotions 3 | In order to handle product promotions 4 | As a store owner 5 | I want to apply promotion discounts during checkout 6 | 7 | Background: 8 | Given the following promotions exist: 9 | | name | description | starts | ends | 10 | | Decade | 20 EUR off for this decade | 2013-01-01 | 2023-01-01 | 11 | | Too late | too late to get this discount | | 2013-01-01 | 12 | | Too soon | too soon to get this discount | 2023-01-01 | | 13 | And promotion "Decade" has following actions defined: 14 | | type | configuration | 15 | | Fixed discount | Amount: 20 | 16 | And promotion "Too late" has following actions defined: 17 | | type | configuration | 18 | | Fixed discount | Amount: 30 | 19 | And promotion "Too soon" has following actions defined: 20 | | type | configuration | 21 | | Fixed discount | Amount: 40 | 22 | And there are following taxonomies defined: 23 | | name | 24 | | Category | 25 | And taxonomy "Category" has following taxons: 26 | | Clothing > Debian T-Shirts | 27 | And the following products exist: 28 | | name | price | taxons | 29 | | Buzz | 500 | Debian T-Shirts | 30 | | Potato | 200 | Debian T-Shirts | 31 | | Woody | 125 | Debian T-Shirts | 32 | | Sarge | 25 | Debian T-Shirts | 33 | | Etch | 20 | Debian T-Shirts | 34 | | Lenny | 15 | Debian T-Shirts | 35 | 36 | Scenario: Promotion is applied when the order date corresponds 37 | with promotion dates 38 | Given I am on the store homepage 39 | When I added product "Sarge" to cart, with quantity "8" 40 | Then I should be on the cart summary page 41 | And "Promotion total: (€20.00)" should appear on the page 42 | And "Grand total: €180.00" should appear on the page -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/coffeemachine/02-going-into-business.feature: -------------------------------------------------------------------------------- 1 | Feature: Going into business 2 | 3 | # 4 | # **In order to** goes into Business 5 | # 6 | # **As a** shopkeeper 7 | # 8 | # **I want to** ensure The coffee machine is not free anymore! 9 | # 10 | #

11 | # 12 | #

13 | # 14 | 15 | @payment @takeOrder @tea @sugar @protocol 16 | Scenario: A tea with just enough money 17 | 18 | The drink maker should make the drinks only if the correct amount of money is given 19 | 20 | Given I've inserted 0.40€ in the machine 21 | When I order a "Tea" with 1 sugar 22 | Then the instruction generated should be "T:1:0" 23 | 24 | @payment @takeOrder @tea @sugar @message @notEnoughMoney 25 | Scenario: A tea with not enough money 26 | 27 | If not enough money is provided, we want to send a message to the drink maker. 28 | The message should contains at least the amount of money missing. 29 | 30 | Given I've inserted 0.30€ in the machine 31 | When I order a "Tea" with 1 sugar 32 | Then the instruction generated should be "M:Not enough money 0.10 missing" 33 | 34 | @payment @takeOrder @tea @sugar @message @tooMuchMoney 35 | Scenario: A coffee with more than required money 36 | 37 | If too much money is given, the drink maker will still make the drink according 38 | to the instructions. The machine will handle the return of the correct change. 39 | 40 | Given I've inserted 2€ in the machine 41 | When I order a "Coffee" with 0 sugar 42 | Then the instruction generated should be "C::" 43 | 44 | @payment @takeOrder @tea @coffee @chocolate @message @notEnoughMoney 45 | Scenario Outline: Check missing money 46 | 47 | Given I've inserted € in the machine 48 | When I order a "" with sugar 49 | Then the instruction generated should be "" 50 | 51 | Examples: 52 | | money | drink | n | instruction | 53 | | 0.25 | Coffee | 0 | M:Not enough money 0.25 missing | 54 | | 0.55 | Chocolate | 0 | M:Not enough money 0.05 missing | 55 | | 0.05 | Tea | 1 | M:Not enough money 0.35 missing | 56 | 57 | -------------------------------------------------------------------------------- /tzatziki-core/src/test/resources/tzatziki/junit/coffeemachine/02-going-into-business.feature: -------------------------------------------------------------------------------- 1 | Feature: Going into business 2 | 3 | # 4 | # **In order to** goes into Business 5 | # 6 | # **As a** shopkeeper 7 | # 8 | # **I want to** ensure The coffee machine is not free anymore! 9 | # 10 | #

11 | # 12 | #

13 | # 14 | 15 | @payment @takeOrder @tea @sugar @protocol 16 | Scenario: A tea with just enough money 17 | 18 | The drink maker should make the drinks only if the correct amount of money is given 19 | 20 | Given I've inserted 0.40€ in the machine 21 | When I order a "Tea" with 1 sugar 22 | Then the instruction generated should be "T:1:0" 23 | 24 | @payment @takeOrder @tea @sugar @message @notEnoughMoney 25 | Scenario: A tea with not enough money 26 | 27 | If not enough money is provided, we want to send a message to the drink maker. 28 | The message should contains at least the amount of money missing. 29 | 30 | Given I've inserted 0.30€ in the machine 31 | When I order a "Tea" with 1 sugar 32 | Then the instruction generated should be "M:Not enough money 0.10 missing" 33 | 34 | @payment @takeOrder @tea @sugar @message @tooMuchMoney 35 | Scenario: A coffee with more than required money 36 | 37 | If too much money is given, the drink maker will still make the drink according 38 | to the instructions. The machine will handle the return of the correct change. 39 | 40 | Given I've inserted 2€ in the machine 41 | When I order a "Coffee" with 0 sugar 42 | Then the instruction generated should be "C::" 43 | 44 | @payment @takeOrder @tea @coffee @chocolate @message @notEnoughMoney 45 | Scenario Outline: Check missing money 46 | 47 | Given I've inserted € in the machine 48 | When I order a "" with sugar 49 | Then the instruction generated should be "" 50 | 51 | Examples: 52 | | money | drink | n | instruction | 53 | | 0.25 | Coffee | 0 | M:Not enough money 0.25 missing | 54 | | 0.55 | Chocolate | 0 | M:Not enough money 0.05 missing | 55 | | 0.05 | Tea | 1 | M:Not enough money 0.35 missing | 56 | 57 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/java/Grammar.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.java; 2 | 3 | import com.google.common.collect.FluentIterable; 4 | 5 | /** 6 | * @author @aloyer 7 | */ 8 | public class Grammar { 9 | 10 | private final PackageEntry root; 11 | 12 | public Grammar() { 13 | this.root = new PackageEntry(""); 14 | } 15 | 16 | public void declarePackage(PackageEntry subPkgEntry) { 17 | root.declareSubPackage(subPkgEntry); 18 | } 19 | 20 | public void declareClass(ClassEntry classEntry) { 21 | root.declareClass(classEntry); 22 | } 23 | 24 | public boolean hasEntries() { 25 | return root.hasEntries(); 26 | } 27 | 28 | public FluentIterable packages() { 29 | return root.subPackages(); 30 | } 31 | 32 | public FluentIterable classes() { 33 | return root.classes(); 34 | } 35 | 36 | public FluentIterable matchingEntries(String text) { 37 | return root.matchingEntries(text); 38 | } 39 | 40 | public void traverse(GrammarVisitor visitor) { 41 | for (ClassEntry classEntry : classes()) { 42 | traverse(null, classEntry, visitor); 43 | } 44 | for (PackageEntry packageEntry : packages()) { 45 | traverse(packageEntry, visitor); 46 | } 47 | } 48 | 49 | private void traverse(PackageEntry packageEntry, ClassEntry classEntry, GrammarVisitor visitor) { 50 | visitor.enter(packageEntry, classEntry); 51 | for (MethodEntry methodEntry : classEntry.methods()) { 52 | visitor.visit(packageEntry, classEntry, methodEntry); 53 | } 54 | visitor.leave(packageEntry, classEntry); 55 | } 56 | 57 | private void traverse(PackageEntry packageEntry, GrammarVisitor visitor) { 58 | visitor.enter(packageEntry); 59 | for (PackageEntry subPkgEntry : packageEntry.subPackages()) { 60 | traverse(subPkgEntry, visitor); 61 | } 62 | for (ClassEntry classEntry : packageEntry.classes()) { 63 | traverse(packageEntry, classEntry, visitor); 64 | } 65 | visitor.leave(packageEntry); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/exec/model/ExamplesExec.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec.model; 2 | 3 | import com.google.common.collect.FluentIterable; 4 | import com.google.common.collect.Lists; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author @aloyer 10 | */ 11 | public class ExamplesExec { 12 | private final String keyword; 13 | private final String name; 14 | private List tags = Lists.newArrayList(); 15 | private List comments = Lists.newArrayList(); 16 | private String description; 17 | private List examplesRows; 18 | 19 | public ExamplesExec(String keyword, String name) { 20 | this.keyword = keyword; 21 | this.name = name; 22 | } 23 | 24 | public ExamplesExec declareTags(List tags) { 25 | this.tags.addAll(tags); 26 | return this; 27 | } 28 | 29 | public ExamplesExec declareComments(List comments) { 30 | this.comments.addAll(comments); 31 | return this; 32 | } 33 | 34 | public ExamplesExec declareDescription(String description) { 35 | this.description = description; 36 | return this; 37 | } 38 | 39 | public ExamplesExec declareRows(List examplesRows) { 40 | this.examplesRows = examplesRows; 41 | return this; 42 | } 43 | 44 | public String name() { 45 | return name; 46 | } 47 | 48 | public FluentIterable tags() { 49 | return FluentIterable.from(tags); 50 | } 51 | 52 | public FluentIterable rows() { 53 | return FluentIterable.from(examplesRows); 54 | } 55 | 56 | public ExamplesExec recursiveCopy() { 57 | return new ExamplesExec(keyword, name) 58 | .declareTags(tags) 59 | .declareComments(comments) 60 | .declareDescription(description) 61 | .declareRows(examplesRows); 62 | } 63 | 64 | public int rowCount() { 65 | return examplesRows.size(); 66 | } 67 | 68 | public int columnCount() { 69 | return examplesRows.get(0).cells().size(); 70 | } 71 | 72 | public String keyword() { 73 | return keyword; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/tag/TagDictionary.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.tag; 2 | 3 | import com.google.common.collect.FluentIterable; 4 | import com.google.common.collect.Maps; 5 | 6 | import java.util.Enumeration; 7 | import java.util.Map; 8 | import java.util.Properties; 9 | 10 | /** 11 | * @author @aloyer 12 | */ 13 | public class TagDictionary { 14 | 15 | private static final String PREFIX = "@"; 16 | 17 | private Map tags = Maps.newConcurrentMap(); 18 | 19 | public TagDictionary() { 20 | } 21 | 22 | public TagDictionary clear() { 23 | tags.clear(); 24 | return this; 25 | } 26 | 27 | public FluentIterable tags() { 28 | return FluentIterable.from(tags.values()); 29 | } 30 | 31 | public TagDictionary declareTags(Properties properties) { 32 | Enumeration propertyNames = properties.propertyNames(); 33 | while (propertyNames.hasMoreElements()) { 34 | String tagKey = (String) propertyNames.nextElement(); 35 | declareTag(tagKey, properties.getProperty(tagKey)); 36 | } 37 | return this; 38 | } 39 | 40 | public TagDictionary declareTag(String tagKey, String description) { 41 | getOrInitTag(tagKey).declareDescription(description); 42 | return this; 43 | } 44 | 45 | private Tag getOrInitTag(String tagKey) { 46 | String formatted = format(tagKey); 47 | Tag tag = tags.get(formatted); 48 | if (tag == null) { 49 | tag = new Tag(formatted); 50 | tags.put(formatted, tag); 51 | } 52 | return tag; 53 | } 54 | 55 | private String format(String tagKey) { 56 | String formatted = tagKey.trim(); 57 | if (!formatted.startsWith(PREFIX)) 58 | return PREFIX + formatted; 59 | else 60 | return formatted; 61 | } 62 | 63 | public void declareTags(TagDictionary dictionary) { 64 | tags.putAll(dictionary.tags); 65 | } 66 | 67 | public boolean containsTag(String tag) { 68 | return tags.containsKey(format(tag)); 69 | } 70 | 71 | public TagDictionary declareTag(String tag) { 72 | return declareTag(tag, ""); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/exec/model/StepContainer.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec.model; 2 | 3 | import com.google.common.collect.FluentIterable; 4 | import com.google.common.collect.Lists; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author @aloyer 10 | */ 11 | public class StepContainer extends EmbeddingAndWriteContainer implements HasComments, HasTags { 12 | 13 | private List steps = Lists.newArrayList(); 14 | private List tags = Lists.newArrayList(); 15 | private List comments = Lists.newArrayList(); 16 | private String description; 17 | private LineRange lineRange; 18 | 19 | protected void recursiveCopy(final StepContainer copy) { 20 | for (StepExec stepExec : steps()) { 21 | copy.declareStep(stepExec.recursiveCopy()); 22 | } 23 | copy.declareTags(tags); 24 | copy.declareComments(comments); 25 | copy.declareDescription(description); 26 | copy.declareLineRange(lineRange); 27 | super.recursiveCopy(copy); 28 | } 29 | 30 | 31 | public void declareStep(StepExec stepExec) { 32 | if (stepExec == null) 33 | throw new IllegalArgumentException("Step cannot be null!"); 34 | steps.add(stepExec); 35 | } 36 | 37 | public void declareTags(List tags) { 38 | this.tags.addAll(tags); 39 | } 40 | 41 | @Override 42 | public FluentIterable tags() { 43 | return FluentIterable.from(tags); 44 | } 45 | 46 | public void declareComments(List comments) { 47 | this.comments.addAll(comments); 48 | } 49 | 50 | public void declareDescription(String description) { 51 | this.description = description; 52 | } 53 | 54 | public FluentIterable steps() { 55 | return FluentIterable.from(steps); 56 | } 57 | 58 | public void declareLineRange(LineRange lineRange) { 59 | this.lineRange = lineRange; 60 | } 61 | 62 | public LineRange lineRange() { 63 | return lineRange; 64 | } 65 | 66 | public String description() { 67 | return description; 68 | } 69 | 70 | @Override 71 | public FluentIterable comments() { 72 | return FluentIterable.from(comments); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tzatziki-core/src/test/java/tzatziki/exploratory/cucumber/TagExpressionTest.java: -------------------------------------------------------------------------------- 1 | package tzatziki.exploratory.cucumber; 2 | 3 | import com.google.common.base.Function; 4 | import com.google.common.collect.FluentIterable; 5 | import gherkin.TagExpression; 6 | import gherkin.formatter.model.Tag; 7 | import org.junit.Test; 8 | 9 | import java.util.List; 10 | 11 | import static java.util.Arrays.asList; 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | 14 | /** 15 | * @author @aloyer 16 | */ 17 | public class TagExpressionTest { 18 | 19 | @Test 20 | public void sampleCase_AND() { 21 | TagExpression e = new TagExpression(asList("~@wip", "~@defaults")); 22 | assertThat(e.evaluate(tags("@bar"))).isTrue(); 23 | assertThat(e.evaluate(tags("@specs"))).isTrue(); 24 | assertThat(e.evaluate(tags("@specs", "@defaults"))).isFalse(); 25 | } 26 | 27 | @Test 28 | public void sampleCase_OR() { 29 | TagExpression e = new TagExpression(asList("~@wip", "@option", "@modify,@insert")); 30 | assertThat(e.evaluate(tags("@wip", "@option", "@modify", "@otc"))).isFalse(); 31 | assertThat(e.evaluate(tags("@specs", "@option", "@modify", "@otc"))).isTrue(); 32 | assertThat(e.evaluate(tags("@acceptance", "@option", "@insert", "@modify", "@otc"))).isTrue(); 33 | } 34 | 35 | @Test 36 | public void sampleCase_OR_withNegation() { 37 | TagExpression e = new TagExpression(asList("~@wip, ~@defaults")); 38 | assertThat(e.evaluate(tags("@bar"))).isTrue(); 39 | assertThat(e.evaluate(tags("@specs"))).isTrue(); 40 | assertThat(e.evaluate(tags("@specs", "@defaults"))).isTrue(); 41 | assertThat(e.evaluate(tags("@wip", "@defaults"))).isFalse(); 42 | assertThat(e.evaluate(tags("@wip"))).isTrue(); 43 | assertThat(e.evaluate(tags("@defaults"))).isTrue(); 44 | } 45 | 46 | private static List tags(String... names) { 47 | return FluentIterable.from(asList(names)).transform(new Function() { 48 | @Override 49 | public Tag apply(String input) { 50 | return tag(input); 51 | } 52 | }).toList(); 53 | } 54 | 55 | private static Tag tag(String name) { 56 | return new Tag(name, 1); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /tzatziki-core/src/test/java/tzatziki/analysis/java/stepdefs/OptionStepdefs.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.java.stepdefs; 2 | 3 | import cucumber.api.PendingException; 4 | import cucumber.api.Scenario; 5 | import cucumber.api.java.Before; 6 | import cucumber.api.java.en.Given; 7 | import cucumber.api.java.en.Then; 8 | import cucumber.api.java.en.When; 9 | 10 | /** 11 | * @author @aloyer 12 | */ 13 | public class OptionStepdefs { 14 | private Scenario scenario; 15 | private String message; 16 | 17 | @Before("@math") 18 | public void initWithMathSupport(Scenario scenario) { 19 | scenario.embed( 20 | ("Value = \\dfrac{ Why }{ How }").getBytes(), "text/formula"); 21 | } 22 | 23 | @Before 24 | public void init(Scenario scenario) { 25 | this.scenario = scenario; 26 | } 27 | 28 | /** 29 | * Order a drink with a number of sugar. 30 | * If the drink does not support the addition of sugar it won't 31 | * be checked here ({@link String}). 32 | * 33 | * @param drinkType type of drink 34 | * @param nbSugar number of sugar (if applicable) 35 | * @throws Throwable 36 | */ 37 | @When("^I order an? \"([^\"]*)\" with (\\d+) sugar$") 38 | public void I_order_a_with_sugar(String drinkType, int nbSugar) throws Throwable { 39 | scenario.embed( 40 | ("" + 41 | " /---------+ +------------+\n" + 42 | " | Order |---->| Protocol |\n" + 43 | " +---------/ +------------+").getBytes(), "text/asciidiag" 44 | ); 45 | } 46 | 47 | @Then("^the instruction generated should be \"([^\"]*)\"$") 48 | public void the_instruction_generated_should_be(String expectedProtocol) throws Throwable { 49 | } 50 | 51 | @When("^the message \"([^\"]*)\" is sent$") 52 | public void the_message_is_sent(String message) throws Throwable { 53 | this.message = message; 54 | } 55 | 56 | @Given("^I've inserted (\\d+)€ in the machine$") 57 | public void I_ve_inserted_€_in_the_machine(int amountInEuro) throws Throwable { 58 | throw new PendingException(); 59 | } 60 | 61 | @Then("^the report output should be$") 62 | public void the_report_output_should_be(String rawReport) throws Throwable { 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/step/FeatureParser.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.step; 2 | 3 | import com.google.common.collect.Lists; 4 | import cucumber.runtime.FeatureBuilder; 5 | import cucumber.runtime.io.MultiLoader; 6 | import cucumber.runtime.io.ResourceLoader; 7 | import cucumber.runtime.model.CucumberFeature; 8 | import gherkin.formatter.Formatter; 9 | import tzatziki.util.ResourceLoaderWrapper; 10 | import tzatziki.util.Filter; 11 | import tzatziki.util.Filters; 12 | 13 | import java.io.File; 14 | import java.io.InputStream; 15 | import java.util.List; 16 | 17 | /** 18 | * @author @aloyer 19 | */ 20 | public class FeatureParser { 21 | 22 | private List cucumberFeatures; 23 | private Formatter formatter; 24 | private String suffix = ""; 25 | // 26 | private List featurePaths = Lists.newArrayList(); 27 | private List filters = Lists.newArrayList(); 28 | private CucumberConverter converter = new CucumberConverter(); 29 | 30 | public FeatureParser() { 31 | cucumberFeatures = Lists.newArrayList(); 32 | formatter = new FeatureBuilder(cucumberFeatures); 33 | } 34 | 35 | public FeatureParser usingSourceDirectory(File sourceTree) { 36 | featurePaths.add(sourceTree.getAbsolutePath()); 37 | return this; 38 | } 39 | 40 | public Features process() { 41 | ResourceLoader resourceLoader = createResourceLoader(getClass()); 42 | 43 | Features features = new Features(); 44 | for (CucumberFeature cucumberFeature : CucumberFeature.load(resourceLoader, featurePaths, filters)) { 45 | features.add(converter.convert(cucumberFeature)); 46 | } 47 | 48 | return features; 49 | } 50 | 51 | protected ResourceLoader createResourceLoader(Class clazz) { 52 | ClassLoader classLoader = clazz.getClassLoader(); 53 | MultiLoader loader = new MultiLoader(classLoader); 54 | 55 | List> filters = instanciateFilters(clazz); 56 | if (filters.isEmpty()) 57 | return loader; 58 | else 59 | return new ResourceLoaderWrapper(loader, Filters.chain(filters)); 60 | } 61 | 62 | private List> instanciateFilters(Class clazz) { 63 | return Lists.newArrayList(); 64 | } 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /tzatziki-pdf/src/main/java/tzatziki/pdf/emitter/StepContainerEmitter.java: -------------------------------------------------------------------------------- 1 | package tzatziki.pdf.emitter; 2 | 3 | import com.google.common.base.Optional; 4 | import gutenberg.itext.ITextContext; 5 | import gutenberg.itext.model.Markdown; 6 | import org.apache.commons.lang3.StringUtils; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import tzatziki.analysis.exec.model.EmbeddingAndWriteContainer; 10 | import tzatziki.analysis.exec.model.HasTags; 11 | import tzatziki.analysis.exec.model.StepContainer; 12 | import tzatziki.analysis.exec.model.StepExec; 13 | import tzatziki.pdf.Comments; 14 | import tzatziki.pdf.model.Steps; 15 | import tzatziki.pdf.model.Tags; 16 | 17 | /** 18 | * @author @aloyer 19 | */ 20 | public class StepContainerEmitter { 21 | 22 | private Logger log = LoggerFactory.getLogger(BackgroundEmitter.class); 23 | 24 | public void emitTags(HasTags hasTags, ITextContext emitterContext) { 25 | emitterContext.emit(Tags.class, new Tags(hasTags.tags())); 26 | } 27 | 28 | public void emitSteps(StepContainer stepContainer, ITextContext emitterContext) { 29 | emitterContext.emit(Steps.class, new Steps(stepContainer.steps())); 30 | } 31 | 32 | public void emitEmbeddings(EmbeddingAndWriteContainer scenario, ITextContext emitterContext) { 33 | } 34 | 35 | public void emitDescription(StepContainer stepContainer, ITextContext emitterContext) { 36 | // Description 37 | StringBuilder b = new StringBuilder(); 38 | String description = stepContainer.description(); 39 | if (StringUtils.isNotBlank(description)) { 40 | b.append(description); 41 | } 42 | 43 | Optional first = stepContainer.steps().first(); 44 | if (first.isPresent()) { 45 | StepExec stepExec = first.get(); 46 | for (String comment : stepExec.comments()) { 47 | String uncommented = Comments.discardCommentChar(comment); 48 | if (!Comments.startsWithComment(uncommented)) { // double # case 49 | b.append(uncommented).append(Comments.NL); 50 | } 51 | } 52 | } 53 | 54 | if (b.length() > 0) { 55 | log.debug("Description content >>{}<<", b); 56 | emitterContext.emit(Markdown.class, new Markdown(b.toString())); 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/stylius/frontend/cart_tax_categories.feature: -------------------------------------------------------------------------------- 1 | @checkout 2 | Feature: Tax categories 3 | In order to handle different types of merchandise 4 | As a store owner 5 | I want to apply taxes depending on the item cateogry 6 | 7 | Background: 8 | Given there are following taxonomies defined: 9 | | name | 10 | | Category | 11 | And taxonomy "Category" has following taxons: 12 | | Clothing > PHP T-Shirts | 13 | | Food > Fruits | 14 | And the following zones are defined: 15 | | name | type | members | 16 | | UK | country | United Kingdom | 17 | And there are following tax categories: 18 | | name | 19 | | Clothing | 20 | | Food | 21 | And the following tax rates exist: 22 | | category | zone | name | amount | 23 | | Clothing | UK | Clothing VAT | 19% | 24 | | Food | UK | Food VAT | 7% | 25 | And the following products exist: 26 | | name | price | taxons | tax category | 27 | | PHP Top | 50 | PHP T-Shirts | Clothing | 28 | | Golden Apple | 120 | Food | Food | 29 | 30 | Scenario: Correct taxes are applied for one item 31 | Given the default tax zone is "UK" 32 | And I am on the store homepage 33 | And I follow "PHP T-Shirts" 34 | And I click "PHP Top" 35 | When I fill in "Quantity" with "2" 36 | And I press "Add to cart" 37 | Then I should be on the cart summary page 38 | And I should see "Clothing VAT (19%) €19.00" 39 | And "Tax total: €19.00" should appear on the page 40 | And "Grand total: €119.00" should appear on the page 41 | 42 | Scenario: Tax rates are applied accordingly to items of both categories 43 | Given the default tax zone is "UK" 44 | And I added product "PHP Top" to cart 45 | And I added product "Golden Apple" to cart 46 | When I go to the cart summary page 47 | Then I should see "Clothing VAT (19%) €9.50" 48 | And I should see "Food VAT (7%) €8.40" 49 | And "Tax total: €17.90" should appear on the page 50 | And "Grand total: €187.90" should appear on the page 51 | -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/GrammarConsolidation.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis; 2 | 3 | import com.google.common.collect.FluentIterable; 4 | import tzatziki.analysis.java.Grammar; 5 | import tzatziki.analysis.java.MethodEntry; 6 | import tzatziki.analysis.java.UsedBy; 7 | import tzatziki.analysis.step.Feature; 8 | import tzatziki.analysis.step.FeatureVisitorAdapter; 9 | import tzatziki.analysis.step.Features; 10 | import tzatziki.analysis.step.Scenario; 11 | import tzatziki.analysis.step.ScenarioOutline; 12 | import tzatziki.analysis.step.Step; 13 | 14 | /** 15 | * @author @aloyer 16 | */ 17 | public class GrammarConsolidation { 18 | private final Grammar grammar; 19 | private final Features features; 20 | 21 | public GrammarConsolidation(Grammar grammar, Features features) { 22 | this.grammar = grammar; 23 | this.features = features; 24 | } 25 | 26 | public void consolidate() { 27 | features.traverse(new Consolidator(grammar)); 28 | } 29 | 30 | private static class Consolidator extends FeatureVisitorAdapter { 31 | private final Grammar grammar; 32 | // 33 | private String featureUri; 34 | private String scenarioName; 35 | private String scenarioOutlineName; 36 | 37 | public Consolidator(Grammar grammar) { 38 | this.grammar = grammar; 39 | } 40 | 41 | @Override 42 | public void enterFeature(Feature feature) { 43 | featureUri = feature.uri(); 44 | } 45 | 46 | @Override 47 | public void enterScenario(Scenario scenario) { 48 | scenarioName = scenario.getVisualName(); 49 | } 50 | 51 | @Override 52 | public void enterScenarioOutline(ScenarioOutline scenario) { 53 | scenarioOutlineName = scenario.getVisualName(); 54 | } 55 | 56 | @Override 57 | public void visitStep(Step step) { 58 | String text = step.getText(); 59 | FluentIterable methodEntries = grammar.matchingEntries(text); 60 | step.grammarMatchCount(methodEntries.size()); 61 | 62 | UsedBy usedBy = new UsedBy(featureUri, scenarioOutlineName, scenarioName); 63 | for (MethodEntry methodEntry : methodEntries) { 64 | methodEntry.declareUsedBy(usedBy); 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tzatziki-core/src/test/java/tzatziki/util/JsonPath.java: -------------------------------------------------------------------------------- 1 | package tzatziki.util; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | 5 | /** 6 | * @author @aloyer 7 | */ 8 | public class JsonPath { 9 | private final String expr; 10 | 11 | public JsonPath(String expr) { 12 | this.expr = expr; 13 | } 14 | 15 | public JsonNode evaluate(JsonNode node) { 16 | return evaluate(node, expr); 17 | } 18 | 19 | public String evaluateString(JsonNode node) { 20 | JsonNode found = evaluate(node); 21 | if (found == null) 22 | return null; 23 | return found.textValue(); 24 | } 25 | 26 | private JsonNode evaluate(JsonNode node, String expr) { 27 | String[] path = removeTrailingSlash(expr).split("/"); 28 | 29 | JsonNode tNode = node; 30 | for (String pathFragment : path) { 31 | tNode = evaluateLocally(tNode, pathFragment); 32 | } 33 | return tNode; 34 | } 35 | 36 | private JsonNode evaluateLocally(JsonNode tNode, String pathFragment) { 37 | if (hasIndexedValue(pathFragment)) { 38 | return indexedValue(tNode, pathFragment); 39 | } 40 | return tNode.get(pathFragment); 41 | } 42 | 43 | private JsonNode indexedValue(JsonNode node, String pathFragment) { 44 | int startOf = pathFragment.lastIndexOf("["); 45 | int endOf = pathFragment.lastIndexOf("]"); 46 | 47 | if (startOf == -1 || endOf == -1) 48 | throw new InvalidExpressionException("Unbalanced '[' and ']' from fragment: '" + pathFragment + "'"); 49 | 50 | String idxAsString = pathFragment.substring(startOf + 1, endOf); 51 | int index = Integer.parseInt(idxAsString); 52 | 53 | String subPath = pathFragment.substring(0, startOf); 54 | JsonNode sub = evaluateLocally(node, subPath); 55 | return sub.get(index); 56 | } 57 | 58 | private boolean hasIndexedValue(String path) { 59 | return path.contains("["); 60 | } 61 | 62 | private static String removeTrailingSlash(String expr) { 63 | return expr.startsWith("/") ? expr.substring(1) : expr; 64 | } 65 | 66 | 67 | public static class InvalidExpressionException extends RuntimeException { 68 | public InvalidExpressionException(String message) { 69 | super(message); 70 | } 71 | } 72 | } 73 | 74 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/stylius/frontend/user_registration.feature: -------------------------------------------------------------------------------- 1 | @users 2 | Feature: User registration 3 | In order to order products 4 | As a visitor 5 | I need to be able to create an account in the store 6 | 7 | Background: 8 | Given there are following users: 9 | | email | password | 10 | | bar@bar.com | foo | 11 | 12 | Scenario: Successfully creating account in store 13 | Given I am on the store homepage 14 | And I follow "Register" 15 | When I fill in the following: 16 | | First name | John | 17 | | Last name | Doe | 18 | | Email | foo@bar.com | 19 | | Password | bar | 20 | | Verification | bar | 21 | And I press "Register" 22 | Then I should see "Welcome" 23 | And I should see "Logout" 24 | 25 | Scenario: Trying to register with non verified password 26 | Given I am on the store homepage 27 | And I follow "Register" 28 | When I fill in the following: 29 | | Email | foo@bar.com | 30 | | Password | bar | 31 | | Verification | foo | 32 | And I press "Register" 33 | Then I should be on registration page 34 | And I should see "The entered passwords don't match" 35 | 36 | Scenario: Trying to register with already existing email 37 | Given I am on the store homepage 38 | And I follow "Register" 39 | When I fill in the following: 40 | | Email | bar@bar.com | 41 | | Password | bar | 42 | | Verification | bar | 43 | And I press "Register" 44 | Then I should be on registration page 45 | And I should see "The email is already used" 46 | 47 | Scenario: Trying to register without first and last name 48 | Given I am on the store homepage 49 | And I follow "Register" 50 | When I fill in the following: 51 | | Email | foo@bar.com | 52 | | Password | bar | 53 | | Verification | bar | 54 | And I press "Register" 55 | Then I should be on registration page 56 | And I should see "Please enter your first name" 57 | And I should see "Please enter your last name" 58 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/stylius/frontend/cart_promotions_complex.feature: -------------------------------------------------------------------------------- 1 | @promotions 2 | Feature: Checkout promotions with multiple rules and actions 3 | In order to handle product promotions 4 | As a store owner 5 | I want to apply promotion discounts during checkout 6 | 7 | Background: 8 | Given the following promotions exist: 9 | | name | description | 10 | | 150 EUR / 2 items | Discount for orders over 150 EUR with at least 2 items | 11 | And promotion "150 EUR / 2 items" has following rules defined: 12 | | type | configuration | 13 | | Item total | Amount: 150 | 14 | | Item count | Count: 2,Equal: true | 15 | And promotion "150 EUR / 2 items" has following actions defined: 16 | | type | configuration | 17 | | Fixed discount | Amount: 20 | 18 | | Percentage discount | Percentage: 5 | 19 | And there are following taxonomies defined: 20 | | name | 21 | | Category | 22 | And taxonomy "Category" has following taxons: 23 | | Clothing > Debian T-Shirts | 24 | And the following products exist: 25 | | name | price | taxons | 26 | | Buzz | 500 | Debian T-Shirts | 27 | | Potato | 200 | Debian T-Shirts | 28 | | Woody | 125 | Debian T-Shirts | 29 | | Sarge | 25 | Debian T-Shirts | 30 | | Etch | 20 | Debian T-Shirts | 31 | | Lenny | 15 | Debian T-Shirts | 32 | 33 | Scenario: Several discounts are applied when a promotion has several 34 | actions and the cart fulfills all the rules 35 | Given I am on the store homepage 36 | And I added product "Sarge" to cart, with quantity "5" 37 | When I add product "Lenny" to cart, with quantity "2" 38 | Then I should be on the cart summary page 39 | And "Promotion total: (€27.75)" should appear on the page 40 | And "Grand total: €127.25" should appear on the page 41 | 42 | Scenario: Promotion is not applied when one of the cart does not 43 | fulfills one of the rule 44 | Given I am on the store homepage 45 | When I add product "Sarge" to cart, with quantity "7" 46 | Then I should be on the cart summary page 47 | And "Promotion total" should not appear on the page 48 | And "Grand total: €175.00" should appear on the page -------------------------------------------------------------------------------- /tzatziki-core/src/main/java/tzatziki/analysis/exec/gson/JsonEmitterReport.java: -------------------------------------------------------------------------------- 1 | package tzatziki.analysis.exec.gson; 2 | 3 | import com.google.gson.Gson; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import tzatziki.analysis.exec.ExecutionReport; 7 | import tzatziki.analysis.exec.model.FeatureExec; 8 | 9 | import java.io.File; 10 | import java.io.FileWriter; 11 | import java.io.IOException; 12 | 13 | /** 14 | * @author @aloyer 15 | */ 16 | public class JsonEmitterReport extends ExecutionReport { 17 | private final Logger log = LoggerFactory.getLogger(JsonEmitterReport.class); 18 | private final File outFile; 19 | private FileWriter writer; 20 | private Gson gson; 21 | private int featureCount = 0; 22 | 23 | public JsonEmitterReport(File reportDir) { 24 | this(reportDir, "exec.json"); 25 | } 26 | 27 | public JsonEmitterReport(File reportDir, String filename) { 28 | outFile = new File(reportDir, filename); 29 | gson = new JsonIO().createGson(); 30 | } 31 | 32 | protected Appendable out() { 33 | if (writer == null) { 34 | try { 35 | outFile.getParentFile().mkdirs(); 36 | writer = new FileWriter(outFile); 37 | log.info("Generating json at {}", outFile.getAbsolutePath()); 38 | writer.append("{\"features\": [\n"); 39 | } catch (IOException ioe) { 40 | throw new ReportException(ioe); 41 | } 42 | } 43 | return writer; 44 | } 45 | 46 | @Override 47 | protected void emit(FeatureExec feature) { 48 | featureCount++; 49 | appendOutSeparatorIfRequired(); 50 | gson.toJson(feature, out()); 51 | } 52 | 53 | private void appendOutSeparatorIfRequired() { 54 | if (featureCount > 1) { 55 | try { 56 | writer.append(",\n"); 57 | } catch (IOException ioe) { 58 | throw new ReportException(ioe); 59 | } 60 | } 61 | } 62 | 63 | @Override 64 | public void close() { 65 | try { 66 | writer.append("]\n}"); 67 | log.info("Json generated at {}", outFile.getAbsolutePath()); 68 | writer.close(); 69 | } catch (IOException e) { 70 | log.error("Oops", e); 71 | } 72 | } 73 | 74 | public static class ReportException extends RuntimeException { 75 | public ReportException(Throwable cause) { 76 | super(cause); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/resources/samples/stylius/frontend/checkout_start.feature: -------------------------------------------------------------------------------- 1 | @checkout 2 | Feature: Checkout starting 3 | In order to buy products 4 | As a visitor 5 | I want to be able to use checkout process 6 | 7 | Background: 8 | Given there are following taxonomies defined: 9 | | name | 10 | | Category | 11 | And taxonomy "Category" has following taxons: 12 | | Clothing > T-Shirts | 13 | | Clothing > PHP T-Shirts | 14 | And there are following options: 15 | | name | presentation | values | 16 | | T-Shirt color | Color | Red, Blue, Green | 17 | And the following products exist: 18 | | name | price | options | taxons | variants selection | 19 | | Super T-Shirt | 20.00 | T-Shirt color | T-Shirts | match | 20 | | PHP Top | 5.99 | | PHP T-Shirts | | 21 | And product "Super T-Shirt" is available in all variations 22 | 23 | Scenario: There is no checkout for empty cart 24 | Given I am on the store homepage 25 | When I follow "View cart" 26 | Then I should be on the cart summary page 27 | And I should see "Your cart is empty" 28 | But I should not see "Checkout" 29 | 30 | Scenario: There is checkout button for filled cart 31 | Given I added product "PHP Top" to cart 32 | When I go to the cart summary page 33 | Then I should see 1 cart item in the list 34 | And I should see "Checkout" 35 | 36 | Scenario: Accessing checkout via cart 37 | Given I added product "PHP Top" to cart 38 | When I go to the cart summary page 39 | And I follow "Checkout" 40 | Then I should be on the checkout security step 41 | 42 | Scenario: Logged in users are starting checkout 43 | from the addressing step 44 | Given I am logged in user 45 | And I added product "PHP Top" to cart 46 | When I go to the checkout start page 47 | Then I should be redirected to the checkout addressing step 48 | 49 | Scenario: Not logged in users need to authenticate or register 50 | new account in the store 51 | Given I added product "PHP Top" to cart 52 | When I go to the checkout start page 53 | Then I should be redirected to the checkout security step 54 | And I should see "Existing Customer" 55 | And I should see "New Customer" 56 | -------------------------------------------------------------------------------- /tzatziki-samples/src/main/java/samples/coffeemachine/TakeOrderSteps.java: -------------------------------------------------------------------------------- 1 | package samples.coffeemachine; 2 | 3 | import cucumber.api.DataTable; 4 | import cucumber.api.PendingException; 5 | import cucumber.api.java.en.Given; 6 | import cucumber.api.java.en.Then; 7 | import cucumber.api.java.en.When; 8 | import org.assertj.core.api.Assertions; 9 | 10 | import static org.mockito.Matchers.eq; 11 | import static org.mockito.Mockito.verify; 12 | 13 | /** 14 | * @author @aloyer 15 | */ 16 | public class TakeOrderSteps { 17 | 18 | private Context context; 19 | 20 | public TakeOrderSteps(Context context) { 21 | this.context = context; 22 | } 23 | 24 | /** 25 | * Provide an array based list of orders 26 | * @param arg1 orders in a table format 27 | * @throws Throwable 28 | */ 29 | @Given("^the following orders:$") 30 | public void the_following_orders(DataTable arg1) throws Throwable { 31 | throw new PendingException(); 32 | } 33 | 34 | @When("^I order an? \"([^\"]*)\"$") 35 | public void I_order_an(String drink) throws Throwable { 36 | context.getGateway().order(drink, 0, false); 37 | } 38 | 39 | @When("^I order an? \"([^\"]*)\" with (\\d+) sugar$") 40 | public void I_order_a_with_sugar(String drink, int nbSugar) throws Throwable { 41 | context.getGateway().order(drink, nbSugar, false); 42 | } 43 | 44 | /** 45 | * Order an extra hot drink with the specified number of sugar 46 | * @param drinkType type of the drink ordered 47 | * @param nbSugar number of sugar 48 | * @throws Throwable 49 | */ 50 | @When("^I order an? extra hot \"([^\"]*)\" with (\\d+) sugar$") 51 | public void I_order_an_extra_hot_with_sugar(String drinkType, int nbSugar) throws Throwable { 52 | Assertions.fail("Extra hot is forbidden in summer!"); 53 | } 54 | 55 | /** 56 | * Send the specified message. 57 | * @param message to send 58 | * @throws Throwable 59 | */ 60 | @When("^the message \"([^\"]*)\" is sent$") 61 | public void the_message_is_sent(String message) throws Throwable { 62 | context.getGateway().publish(message); 63 | } 64 | 65 | /** 66 | * Assert that the generated command matches the specified command. 67 | * @param command expected command 68 | * @throws Throwable 69 | */ 70 | @Then("^the instruction generated should be \"([^\"]*)\"$") 71 | public void the_instruction_generated_should_be(String command) throws Throwable { 72 | verify(context.getDrinkMaker()).executeCommand(eq(command)); 73 | } 74 | 75 | } 76 | --------------------------------------------------------------------------------