Skip to content

Commit ea88e59

Browse files
authored
Find Suggestions (#102)
1 parent c430590 commit ea88e59

18 files changed

+272
-21
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
## [Unreleased]
99
### Added
10-
- Update dependency `io.cucumber:messages` up to v29 ([#101](https://github.com/cucumber/query/pull/101))
10+
- Update dependency `messages` up to v29 ([#101](https://github.com/cucumber/query/pull/101))
1111
- Added more queries to find messages by `TestCaseFinished` and `TestStepFinished` ([#77](https://github.com/cucumber/query/pull/77))
12+
- Added queries to find suggestions by `Pickle` and `PickleStep` ([#102](https://github.com/cucumber/query/pull/102))
1213

1314
### Deprecated
1415
- Deprecated various lineage derived methods ([#84](https://github.com/cucumber/query/pull/84))
1516

1617
### Removed
1718
- [JavaScript] Remove Node.js 18 support ([#84](https://github.com/cucumber/query/pull/84))
19+
- Removed support for `messages` below v29
1820

1921
## [13.6.0] - 2025-08-11
2022
### Changed

java/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
<dependency>
5252
<groupId>io.cucumber</groupId>
5353
<artifactId>messages</artifactId>
54-
<version>[24.0.0,30.0.0)</version>
54+
<version>[29.0.1,30.0.0)</version>
5555
</dependency>
5656

5757
<dependency>

java/src/main/java/io/cucumber/query/Query.java

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import io.cucumber.messages.types.Scenario;
1616
import io.cucumber.messages.types.Step;
1717
import io.cucumber.messages.types.StepDefinition;
18+
import io.cucumber.messages.types.Suggestion;
1819
import io.cucumber.messages.types.TableRow;
1920
import io.cucumber.messages.types.TestCase;
2021
import io.cucumber.messages.types.TestCaseFinished;
@@ -87,6 +88,7 @@ public final class Query {
8788
private final Map<String, List<Attachment>> attachmentsByTestCaseStartedId = new LinkedHashMap<>();
8889
private final Map<Object, Lineage> lineageById = new HashMap<>();
8990
private final Map<String, StepDefinition> stepDefinitionById = new LinkedHashMap<>();
91+
private final Map<String, List<Suggestion>> suggestionsByPickleStepId = new LinkedHashMap<>();
9092
private Meta meta;
9193
private TestRunStarted testRunStarted;
9294
private TestRunFinished testRunFinished;
@@ -159,13 +161,13 @@ public List<TestCase> findAllTestCases() {
159161
return new ArrayList<>(testCaseById.values());
160162
}
161163

162-
public List<TestStepStarted> findAllTestStepsStarted() {
164+
public List<TestStepStarted> findAllTestStepStarted() {
163165
return testStepsStartedByTestCaseStartedId.values().stream()
164166
.flatMap(Collection::stream)
165167
.collect(toList());
166168
}
167169

168-
public List<TestStepFinished> findAllTestStepsFinished() {
170+
public List<TestStepFinished> findAllTestStepFinished() {
169171
return testStepsFinishedByTestCaseStartedId.values().stream()
170172
.flatMap(Collection::stream)
171173
.collect(toList());
@@ -333,12 +335,33 @@ public Optional<Pickle> findPickleBy(TestStepStarted testStepStarted) {
333335
.map(pickleById::get);
334336
}
335337

338+
public Optional<Pickle> findPickleBy(TestStepFinished testStepFinished) {
339+
requireNonNull(testStepFinished);
340+
return findTestCaseBy(testStepFinished)
341+
.map(TestCase::getPickleId)
342+
.map(pickleById::get);
343+
}
344+
336345
public Optional<PickleStep> findPickleStepBy(TestStep testStep) {
337346
requireNonNull(testStep);
338347
return testStep.getPickleStepId()
339348
.map(pickleStepById::get);
340349
}
341350

351+
public List<Suggestion> findSuggestionsBy(PickleStep pickleStep){
352+
requireNonNull(pickleStep);
353+
List<Suggestion> suggestions = suggestionsByPickleStepId.getOrDefault(pickleStep.getId(), emptyList());
354+
return new ArrayList<>(suggestions);
355+
}
356+
357+
public List<Suggestion> findSuggestionsBy(Pickle pickle){
358+
requireNonNull(pickle);
359+
return pickle.getSteps().stream()
360+
.map(this::findSuggestionsBy)
361+
.flatMap(Collection::stream)
362+
.collect(toList());
363+
}
364+
342365
public Optional<Step> findStepBy(PickleStep pickleStep) {
343366
requireNonNull(pickleStep);
344367
String stepId = pickleStep.getAstNodeIds().get(0);
@@ -498,6 +521,7 @@ public void update(Envelope envelope) {
498521
envelope.getTestCase().ifPresent(this::updateTestCase);
499522
envelope.getHook().ifPresent(this::updateHook);
500523
envelope.getAttachment().ifPresent(this::updateAttachment);
524+
envelope.getSuggestion().ifPresent(this::updateSuggestions);
501525
}
502526

503527
public Optional<Lineage> findLineageBy(GherkinDocument element) {
@@ -614,6 +638,10 @@ private void updateSteps(List<Step> steps) {
614638
steps.forEach(step -> stepById.put(step.getId(), step));
615639
}
616640

641+
private void updateSuggestions(Suggestion event) {
642+
this.suggestionsByPickleStepId.compute(event.getPickleStepId(), updateList(event));
643+
}
644+
617645
private void updateMeta(Meta event) {
618646
this.meta = event;
619647
}

java/src/test/java/io/cucumber/query/QueryAcceptanceTest.java

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io.cucumber.messages.types.PickleStep;
1010
import io.cucumber.messages.types.Step;
1111
import io.cucumber.messages.types.StepDefinition;
12+
import io.cucumber.messages.types.Suggestion;
1213
import io.cucumber.messages.types.TestCase;
1314
import io.cucumber.messages.types.TestCaseFinished;
1415
import io.cucumber.messages.types.TestCaseStarted;
@@ -114,9 +115,9 @@ static Map<String, Function<Query, Object>> createQueries() {
114115
queries.put("findAllTestCaseStarted", (query) -> query.findAllTestCaseStarted().size());
115116
queries.put("findAllTestCaseFinished", (query) -> query.findAllTestCaseFinished().size());
116117
queries.put("findAllTestSteps", (query) -> query.findAllTestSteps().size());
117-
queries.put("findAllTestStepsStarted", (query) -> query.findAllTestStepsStarted().size());
118-
queries.put("findAllTestStepsFinished", (query) -> query.findAllTestStepsFinished().size());
119-
queries.put("findAllTestCases", (query) -> query.findAllTestCases().size());
118+
queries.put("findAllTestStepsStarted", (query) -> query.findAllTestStepStarted().size());
119+
queries.put("findAllTestStepsFinished", (query) -> query.findAllTestStepFinished().size());
120+
queries.put("findAllTestCases", (query) -> query.findAllTestCases().size());
120121
queries.put("findAttachmentsBy", (query) -> query.findAllTestCaseStarted().stream()
121122
.map(query::findTestStepFinishedAndTestStepBy)
122123
.flatMap(Collection::stream)
@@ -140,7 +141,7 @@ static Map<String, Function<Query, Object>> createQueries() {
140141
.filter(Optional::isPresent)
141142
.collect(toList()));
142143
queries.put("findMeta", (query) -> query.findMeta().map(meta -> meta.getImplementation().getName()));
143-
144+
144145
queries.put("findMostSevereTestStepResultBy", (query) -> {
145146
Map<String, Object> results = new LinkedHashMap<>();
146147
results.put("testCaseStarted", query.findAllTestCaseStarted().stream()
@@ -160,17 +161,25 @@ static Map<String, Function<Query, Object>> createQueries() {
160161

161162
queries.put("findPickleBy", (query) -> {
162163
Map<String, Object> results = new LinkedHashMap<>();
163-
results.put("testCaseStarted", query.findAllTestCaseStarted().stream()
164+
results.put("testCaseStarted", query.findAllTestCaseStarted().stream()
165+
.map(query::findPickleBy)
166+
.map(pickle -> pickle.map(Pickle::getName))
167+
.collect(toList()));
168+
results.put("testCaseFinished", query.findAllTestCaseFinished().stream()
164169
.map(query::findPickleBy)
165170
.map(pickle -> pickle.map(Pickle::getName))
166171
.collect(toList()));
167-
results.put("testCaseFinished", query.findAllTestCaseFinished().stream()
172+
results.put("testStepStarted", query.findAllTestStepStarted().stream()
173+
.map(query::findPickleBy)
174+
.map(pickle -> pickle.map(Pickle::getName))
175+
.collect(toList()));
176+
results.put("testStepFinished", query.findAllTestStepFinished().stream()
168177
.map(query::findPickleBy)
169178
.map(pickle -> pickle.map(Pickle::getName))
170179
.collect(toList()));
171180
return results;
172181
});
173-
182+
174183
queries.put("findPickleStepBy", (query) -> query.findAllTestSteps().stream()
175184
.map(query::findPickleStepBy)
176185
.map(pickleStep -> pickleStep.map(PickleStep::getText))
@@ -185,6 +194,22 @@ static Map<String, Function<Query, Object>> createQueries() {
185194
.map(stepDefinitions -> stepDefinitions.stream().map(StepDefinition::getId)
186195
.collect(toList()))
187196
.collect(toList()));
197+
198+
queries.put("findSuggestionsBy", (query) -> {
199+
Map<String, Object> results = new LinkedHashMap<>();
200+
results.put("pickleStep", query.findAllPickleSteps().stream()
201+
.map(query::findSuggestionsBy)
202+
.flatMap(Collection::stream)
203+
.map(Suggestion::getId)
204+
.collect(toList()));
205+
results.put("pickle", query.findAllPickles().stream()
206+
.map(query::findSuggestionsBy)
207+
.flatMap(Collection::stream)
208+
.map(Suggestion::getId)
209+
.collect(toList()));
210+
return results;
211+
});
212+
188213
queries.put("findUnambiguousStepDefinitionBy", (query) -> query.findAllTestSteps().stream()
189214
.map(query::findUnambiguousStepDefinitionBy)
190215
.filter(Optional::isPresent)
@@ -193,17 +218,17 @@ static Map<String, Function<Query, Object>> createQueries() {
193218

194219
queries.put("findTestCaseStartedBy", (query) -> {
195220
Map<String, Object> results = new LinkedHashMap<>();
196-
results.put("testStepStarted", query.findAllTestStepsStarted().stream()
221+
results.put("testStepStarted", query.findAllTestStepStarted().stream()
197222
.map(query::findTestCaseStartedBy)
198223
.map(testCase -> testCase.map(TestCaseStarted::getId))
199224
.collect(toList()));
200-
results.put("testStepFinished", query.findAllTestStepsFinished().stream()
225+
results.put("testStepFinished", query.findAllTestStepFinished().stream()
201226
.map(query::findTestCaseStartedBy)
202227
.map(testCase -> testCase.map(TestCaseStarted::getId))
203228
.collect(toList()));
204229
return results;
205230
});
206-
231+
207232
queries.put("findTestCaseBy", (query) -> {
208233
Map<String, Object> results = new LinkedHashMap<>();
209234
results.put("testCaseStarted", query.findAllTestCaseStarted().stream()
@@ -214,17 +239,17 @@ static Map<String, Function<Query, Object>> createQueries() {
214239
.map(query::findTestCaseBy)
215240
.map(testCase -> testCase.map(TestCase::getId))
216241
.collect(toList()));
217-
results.put("testStepStarted", query.findAllTestStepsStarted().stream()
242+
results.put("testStepStarted", query.findAllTestStepStarted().stream()
218243
.map(query::findTestCaseBy)
219244
.map(testCase -> testCase.map(TestCase::getId))
220245
.collect(toList()));
221-
results.put("testStepFinished", query.findAllTestStepsFinished().stream()
246+
results.put("testStepFinished", query.findAllTestStepFinished().stream()
222247
.map(query::findTestCaseBy)
223248
.map(testCase -> testCase.map(TestCase::getId))
224249
.collect(toList()));
225250
return results;
226251
});
227-
252+
228253
queries.put("findTestCaseDurationBy", (query) -> {
229254
Map<String, Object> results = new LinkedHashMap<>();
230255
results.put("testCaseStarted", query.findAllTestCaseStarted().stream()
@@ -237,7 +262,7 @@ static Map<String, Function<Query, Object>> createQueries() {
237262
.collect(toList()));
238263
return results;
239264
});
240-
265+
241266
queries.put("findTestCaseFinishedBy", (query) -> query.findAllTestCaseStarted().stream()
242267
.map(query::findTestCaseFinishedBy)
243268
.map(testCaseFinished -> testCaseFinished.map(TestCaseFinished::getTestCaseStartedId))
@@ -252,7 +277,7 @@ static Map<String, Function<Query, Object>> createQueries() {
252277
.map(query::findTestStepBy)
253278
.map(testStep -> testStep.map(TestStep::getId))
254279
.collect(toList()));
255-
280+
256281
queries.put("findTestStepByTestStepFinished", (query) -> {
257282
Map<String, Object> results = new LinkedHashMap<>();
258283

@@ -271,7 +296,7 @@ static Map<String, Function<Query, Object>> createQueries() {
271296

272297
return results;
273298
});
274-
299+
275300
queries.put("findTestStepsFinishedBy", (query) -> query.findAllTestCaseStarted().stream()
276301
.map(query::findTestStepsFinishedBy)
277302
.map(testStepFinisheds -> testStepFinisheds.stream().map(TestStepFinished::getTestStepId).collect(toList()))

javascript/src/Query.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
Scenario,
1515
Step,
1616
StepDefinition,
17+
Suggestion,
1718
TestCase,
1819
TestCaseFinished,
1920
TestCaseStarted,
@@ -71,6 +72,8 @@ export default class Query {
7172
new ArrayMultimap()
7273
private readonly attachmentsByTestCaseStartedId: ArrayMultimap<string, Attachment> =
7374
new ArrayMultimap()
75+
private readonly suggestionsByPickleStepId: ArrayMultimap<string, Suggestion> =
76+
new ArrayMultimap()
7477

7578
public update(envelope: messages.Envelope) {
7679
if (envelope.meta) {
@@ -112,6 +115,10 @@ export default class Query {
112115
if (envelope.testRunFinished) {
113116
this.testRunFinished = envelope.testRunFinished
114117
}
118+
119+
if (envelope.suggestion) {
120+
this.updateSuggestion(envelope.suggestion)
121+
}
115122
}
116123

117124
private updateGherkinDocument(gherkinDocument: GherkinDocument) {
@@ -259,6 +266,10 @@ export default class Query {
259266
)
260267
}
261268

269+
private updateSuggestion(suggestion: Suggestion) {
270+
this.suggestionsByPickleStepId.put(suggestion.pickleStepId, suggestion)
271+
}
272+
262273
/**
263274
* Gets all the results for multiple pickle steps
264275
* @param pickleStepIds
@@ -573,6 +584,13 @@ export default class Query {
573584
return (testStep.stepDefinitionIds ?? []).map((id) => this.stepDefinitionById.get(id))
574585
}
575586

587+
findSuggestionsBy(element: PickleStep | Pickle): ReadonlyArray<Suggestion> {
588+
if ('steps' in element) {
589+
return element.steps.flatMap((value) => this.findSuggestionsBy(value))
590+
}
591+
return this.suggestionsByPickleStepId.get(element.id)
592+
}
593+
576594
public findUnambiguousStepDefinitionBy(testStep: TestStep): StepDefinition | undefined {
577595
if (testStep.stepDefinitionIds?.length === 1) {
578596
return this.stepDefinitionById.get(testStep.stepDefinitionIds[0])

javascript/src/acceptance.spec.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,14 @@ describe('Acceptance Tests', async () => {
7676
.findAllTestCaseFinished()
7777
.map((testCaseFinished) => query.findPickleBy(testCaseFinished))
7878
.map((pickle) => pickle?.name),
79+
testStepStarted: query
80+
.findAllTestStepFinished()
81+
.map((testCaseStarted) => query.findPickleBy(testCaseStarted))
82+
.map((pickle) => pickle?.name),
83+
testStepFinished: query
84+
.findAllTestStepFinished()
85+
.map((testCaseFinished) => query.findPickleBy(testCaseFinished))
86+
.map((pickle) => pickle?.name),
7987
}
8088
},
8189
findPickleStepBy: (query: Query) =>
@@ -95,6 +103,18 @@ describe('Acceptance Tests', async () => {
95103
.map((pickleStep) =>
96104
query.findStepDefinitionsBy(pickleStep).map((stepDefinition) => stepDefinition?.id)
97105
),
106+
findSuggestionsBy: (query: Query) => {
107+
return {
108+
pickleStep: query
109+
.findAllPickleSteps()
110+
.flatMap((pickleSteps) => query.findSuggestionsBy(pickleSteps))
111+
.map((suggestion) => suggestion.id),
112+
pickle: query
113+
.findAllPickles()
114+
.flatMap((pickle) => query.findSuggestionsBy(pickle))
115+
.map((suggestion) => suggestion.id),
116+
}
117+
},
98118
findUnambiguousStepDefinitionBy: (query: Query) =>
99119
query
100120
.findAllTestSteps()

testdata/src/attachments.findPickleBy.results.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,23 @@
1616
"Byte arrays are base64-encoded regardless of media type",
1717
"Attaching PDFs with a different filename",
1818
"Attaching URIs"
19+
],
20+
"testStepStarted" : [
21+
"Strings can be attached with a media type",
22+
"Log text",
23+
"Log ANSI coloured text",
24+
"Log JSON",
25+
"Byte arrays are base64-encoded regardless of media type",
26+
"Attaching PDFs with a different filename",
27+
"Attaching URIs"
28+
],
29+
"testStepFinished" : [
30+
"Strings can be attached with a media type",
31+
"Log text",
32+
"Log ANSI coloured text",
33+
"Log JSON",
34+
"Byte arrays are base64-encoded regardless of media type",
35+
"Attaching PDFs with a different filename",
36+
"Attaching URIs"
1937
]
2038
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"pickleStep" : [ ],
3+
"pickle" : [ ]
4+
}

testdata/src/empty.findPickleBy.results.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,7 @@
44
],
55
"testCaseFinished" : [
66
"Blank Scenario"
7-
]
7+
],
8+
"testStepStarted" : [ ],
9+
"testStepFinished" : [ ]
810
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"pickleStep" : [ ],
3+
"pickle" : [ ]
4+
}

0 commit comments

Comments
 (0)