11package com .pdsl .gherkin .models ;
22
3- import java .util . ArrayList ;
4- import java .util .List ;
5- import java .util .Optional ;
3+ import java .net . URI ;
4+ import java .util .* ;
5+ import java .util .stream . Collectors ;
66
77public class GherkinScenario {
88 private final Optional <List <String >> tags ;
99 private final Optional <GherkinString > title ;
1010 private final Optional <GherkinString > longDescription ;
1111 private final Optional <List <GherkinStep >> stepsList ;
1212 private final Optional <List <GherkinExamplesTable >> examples ;
13+ private final Optional <ScenarioPosition > scenarioPosition ;
1314 private final int lineNumber ;
1415
1516 public GherkinScenario (Builder builder ) {
@@ -22,8 +23,118 @@ public GherkinScenario(Builder builder) {
2223 this .examples = builder .examples .isEmpty () ? Optional .empty ()
2324 : Optional .of (builder .examples );
2425 this .lineNumber = builder .lineNumber ;
26+ this .scenarioPosition = builder .scenarioPosition ;
2527 }
2628
29+ /**
30+ * A specification of the hierarchical position this scenario appeared in with relation to other scenarios.
31+ * <p>
32+ * The ruleIndex specifies which rule the scenario was found. [e.g. which rule it was found in]
33+ * If the test was not nested in a rule the value will be 0
34+ * <p>
35+ * The ordinal specifies which order this test showed up in relation to the others in the same rule index.
36+ * <p>
37+ * The testIndex specifies is the test was derived from an examples table. If it was, it is the
38+ * nth row it appeared in starting from one. If it was not in an examples table the number will be 0.
39+ * <p>
40+ * This encoding CANNOT guarantee whether an arbitrary scenario was declared before another as it is possible
41+ * to intersperse rule nodes between root level scenarios. If you want to know the prceise order the test
42+ * was declared in the original source file use #getLineNumber() and compare using the
43+ * {@see com.pdsl.testcases.DefaultTestCase.PdslTestCaseComparator}
44+ * <p>
45+ * For example:
46+ * <p>
47+ * <pre>
48+ * {@code
49+ * Feature:
50+ * Scenario:
51+ * # First part is "0" because it is a root node. Last part is "0" because it is not in a table.
52+ * Then this group ordinal is 0.1.0
53+ * Scenario:
54+ * Then this group ordinal is 0.2.0 # The second test in the root, se we increment to 2.
55+ * Scenario:
56+ * Then this group ordinal is <ORDINAL>
57+ * Examples:
58+ * |ORDINAL|
59+ * | 0.3.1 | # Second test in the root, so we increment the second value to 2
60+ * | 0.3.2 | # Increment last digit as it comes from the same group
61+ * | 0.3.3 |
62+ *
63+ * Rule: First rule (1)
64+ * Scenario:
65+ * Then this group ordinal is 1.1.0
66+ * Scenario:
67+ * Then this group ordinal is 1.2.0
68+ * Scenario:
69+ * Then this group oridinal is <ORDINAL>
70+ * Examples:
71+ * |ORDINAL|
72+ * | 1.3.1 |
73+ * | 1.3.2 |
74+ *
75+ * # Multi-tables continue from the index used in the last table
76+ * Examples:
77+ * |ORDINAL|
78+ * | 1.3.3 |
79+ * | 1.3.4
80+ *
81+ * Scenario:
82+ * Then this group ordinal is 0.4.0 # Note we're back at root and continue from the last testPosition
83+ *
84+ * Rule: Second rule (2)
85+ * Scenario:
86+ * Then this group ordinal is 2.1.0
87+ * }
88+ * </pre>
89+ *
90+ * @param ruleIndex the nth rule this scenario was derived from, 0 if not in a rule
91+ * @param ordinal the nth position of this scenario relative to others in the same depth
92+ * @param testIndex 0 if not derived from an examples table, otherwise the nth row starting from 1
93+ */
94+ public record ScenarioPosition (int ruleIndex , int ordinal , int testIndex ) implements Comparable <ScenarioPosition > {
95+
96+ public static String RULE_INDEX = "ruleIndex" ;
97+ public static String ORDINAL = "ordinal" ;
98+ public static String TABLE_INDEX = "tableIndex" ;
99+ private static final ScenarioPositionComparator SINGLETON = new ScenarioPositionComparator ();
100+
101+ private static class ScenarioPositionComparator implements Comparator <ScenarioPosition > {
102+ @ Override
103+ public int compare (ScenarioPosition p1 , ScenarioPosition p2 ) {
104+ if (p1 .ruleIndex != p2 .ruleIndex ) {
105+ return Integer .compare (p1 .ruleIndex , p2 .ruleIndex );
106+ }
107+ if (p1 .ordinal != p2 .ordinal ) {
108+ return Integer .compare (p1 .ordinal , p2 .ordinal );
109+ }
110+ return Integer .compare (p1 .testIndex , p2 .testIndex );
111+ }
112+ }
113+
114+ @ Override
115+ public int compareTo (ScenarioPosition scenarioPosition ) {
116+ return SINGLETON .compare (this , scenarioPosition );
117+ }
118+
119+ public static Optional <ScenarioPosition > from (URI uri ) {
120+ Map <String , String > params = Arrays .stream (uri .getQuery ().split ("&" ))
121+ .map (param -> param .split ("=" ))
122+ .filter (arr -> arr .length == 2 )
123+ .collect (Collectors .toMap (arr -> arr [0 ], arr -> arr [1 ]));
124+ try {
125+ int ruleIndex = Integer .parseInt (params .get (RULE_INDEX ));
126+ int ordinal = Integer .parseInt (params .get (ORDINAL ));
127+ int tableIndex = Integer .parseInt (params .get (TABLE_INDEX ));
128+ return Optional .of (new ScenarioPosition (ruleIndex , ordinal , tableIndex ));
129+ } catch (RuntimeException e ) {
130+ return Optional .empty ();
131+ }
132+ }
133+ }
134+
135+
136+ public Optional <ScenarioPosition > getScenarioPositition () { return scenarioPosition ; }
137+
27138 public Optional <List <String >> getTags () {
28139 return tags ;
29140 }
@@ -55,11 +166,17 @@ public static class Builder {
55166 private String longDescription = "" ;
56167 private Optional <List <GherkinStep >> stepsList = Optional .empty ();
57168 private int lineNumber = -1 ;
169+ private Optional <ScenarioPosition > scenarioPosition = Optional .empty ();
58170
59171 public GherkinScenario build () {
60172 return new GherkinScenario (this );
61173 }
62174
175+ public Builder withScenarioPosition (ScenarioPosition scenarioPosition ) {
176+ this .scenarioPosition = Optional .ofNullable (scenarioPosition );
177+ return this ;
178+ }
179+
63180 public Builder addExamples (GherkinExamplesTable examples ) {
64181 this .examples .add (examples );
65182 return this ;
0 commit comments