Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]
### Added
- [Java, JavaScript] Add `Lineage.background()` and `.ruleBackground()` ([#140](https://github.com/cucumber/query/pull/140))
### Removed
- [JavaScript] BREAKING CHANGE: Remove defunct legacy methods from `Query` ([#141](https://github.com/cucumber/query/pull/141))

Expand Down
25 changes: 25 additions & 0 deletions java/src/main/java/io/cucumber/query/Lineage.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package io.cucumber.query;

import io.cucumber.messages.types.Background;
import io.cucumber.messages.types.Examples;
import io.cucumber.messages.types.Feature;
import io.cucumber.messages.types.FeatureChild;
import io.cucumber.messages.types.GherkinDocument;
import io.cucumber.messages.types.Rule;
import io.cucumber.messages.types.RuleChild;
import io.cucumber.messages.types.Scenario;
import io.cucumber.messages.types.TableRow;

Expand Down Expand Up @@ -74,10 +77,32 @@ public Optional<Feature> feature() {
return Optional.ofNullable(feature);
}

public Optional<Background> background() {
if (feature == null) {
return Optional.empty();
}
return feature.getChildren().stream()
.map(FeatureChild::getBackground)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
}

public Optional<Rule> rule() {
return Optional.ofNullable(rule);
}

public Optional<Background> ruleBackground() {
if (rule == null) {
return Optional.empty();
}
return rule.getChildren().stream()
.map(RuleChild::getBackground)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
}

public Optional<Scenario> scenario() {
return Optional.ofNullable(scenario);
}
Expand Down
2 changes: 0 additions & 2 deletions java/src/main/java/io/cucumber/query/Lineages.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;

class Lineages {

Expand Down
128 changes: 128 additions & 0 deletions java/src/test/java/io/cucumber/query/LineageTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package io.cucumber.query;

import io.cucumber.messages.NdjsonToMessageIterable;
import io.cucumber.messages.types.Background;
import io.cucumber.messages.types.Envelope;
import io.cucumber.messages.types.Examples;
import io.cucumber.messages.types.Feature;
import io.cucumber.messages.types.GherkinDocument;
import io.cucumber.messages.types.Pickle;
import io.cucumber.messages.types.Rule;
import io.cucumber.messages.types.Scenario;
import io.cucumber.messages.types.TableRow;
import org.jspecify.annotations.NonNull;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static io.cucumber.query.Repository.RepositoryFeature.INCLUDE_GHERKIN_DOCUMENTS;
import static org.assertj.core.api.Assertions.assertThat;

class LineageTest {

final Repository repository = Repository.builder()
.feature(INCLUDE_GHERKIN_DOCUMENTS, true)
.build();
final Query query = new Query(repository);

@Test
void minimal() throws IOException {
List<Envelope> messages = readMessages(Paths.get("../testdata/src/minimal.ndjson"));
messages.forEach(repository::update);
Pickle pickle = query.findAllPickles().stream()
.findFirst()
.get();
Lineage lineage = query.findLineageBy(pickle).get();

GherkinDocument gherkinDocument = messages.stream().filter(envelope -> envelope.getGherkinDocument().isPresent())
.map(Envelope::getGherkinDocument)
.map(Optional::get)
.findFirst()
.get();
Optional<Feature> feature = gherkinDocument.getFeature();
Optional<Scenario> scenario = feature.get().getChildren().get(0).getScenario();

assertThat(lineage.document()).isEqualTo(gherkinDocument);
assertThat(lineage.feature()).isEqualTo(feature);
assertThat(lineage.background()).isEmpty();
assertThat(lineage.rule()).isEmpty();
assertThat(lineage.ruleBackground()).isEmpty();
assertThat(lineage.scenario()).isEqualTo(scenario);
assertThat(lineage.examples()).isEmpty();
assertThat(lineage.example()).isEmpty();
}

@Test
void exampleTables() throws IOException {
List<Envelope> messages = readMessages(Paths.get("../testdata/src/examples-tables.ndjson"));
messages.forEach(repository::update);
Pickle pickle = query.findAllPickles().stream()
.findFirst()
.get();
Lineage lineage = query.findLineageBy(pickle).get();

GherkinDocument gherkinDocument = messages.stream().filter(envelope -> envelope.getGherkinDocument().isPresent())
.map(Envelope::getGherkinDocument)
.map(Optional::get)
.findFirst()
.get();
Optional<Feature> feature = gherkinDocument.getFeature();
Optional<Scenario> scenario = feature.get().getChildren().get(0).getScenario();
Examples examples = scenario.get().getExamples().get(0);
TableRow example = examples.getTableBody().get(0);

assertThat(lineage.document()).isEqualTo(gherkinDocument);
assertThat(lineage.feature()).isEqualTo(feature);
assertThat(lineage.background()).isEmpty();
assertThat(lineage.rule()).isEmpty();
assertThat(lineage.ruleBackground()).isEmpty();
assertThat(lineage.scenario()).isEqualTo(scenario);
assertThat(lineage.examples()).contains(examples);
assertThat(lineage.example()).contains(example);
}

@Test
void rulesBackgrounds() throws IOException {
List<Envelope> messages = readMessages(Paths.get("../testdata/src/rules-backgrounds.ndjson"));
messages.forEach(repository::update);
Pickle pickle = query.findAllPickles().stream()
.findFirst()
.get();
Lineage lineage = query.findLineageBy(pickle).get();

GherkinDocument gherkinDocument = messages.stream().filter(envelope -> envelope.getGherkinDocument().isPresent())
.map(Envelope::getGherkinDocument)
.map(Optional::get)
.findFirst()
.get();
Optional<Feature> feature = gherkinDocument.getFeature();
Optional<Background> background = feature.get().getChildren().get(0).getBackground();
Optional<Rule> rule = feature.get().getChildren().get(1).getRule();
Optional<Background> ruleBackGround = rule.get().getChildren().get(0).getBackground();
Optional<Scenario> scenario = rule.get().getChildren().get(1).getScenario();

assertThat(lineage.document()).isEqualTo(gherkinDocument);
assertThat(lineage.feature()).isEqualTo(feature);
assertThat(lineage.background()).isEqualTo(background);
assertThat(lineage.rule()).isEqualTo(rule);
assertThat(lineage.ruleBackground()).isEqualTo(ruleBackGround);
assertThat(lineage.scenario()).isEqualTo(scenario);
assertThat(lineage.examples()).isEmpty();
assertThat(lineage.example()).isEmpty();
}

private static @NonNull List<Envelope> readMessages(Path path) throws IOException {
InputStream in = Files.newInputStream(path);
NdjsonToMessageIterable messages = new NdjsonToMessageIterable(in, json -> Jackson.OBJECT_MAPPER.readValue(json, Envelope.class));
List<Envelope> e = new ArrayList<>();
messages.forEach(e::add);
return e;
}
}
3 changes: 3 additions & 0 deletions javascript/src/Lineage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
Background,
Examples,
Feature,
GherkinDocument,
Expand All @@ -11,7 +12,9 @@ import {
export interface Lineage {
gherkinDocument?: GherkinDocument
feature?: Feature
background?: Background
rule?: Rule
ruleBackground?: Background
scenario?: Scenario
examples?: Examples
examplesIndex?: number
Expand Down
90 changes: 89 additions & 1 deletion javascript/src/Query.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import assert from 'node:assert'
import fs from 'node:fs/promises'
import path from 'node:path'

import { TestCaseStarted } from '@cucumber/messages'
import { Envelope, TestCaseStarted } from '@cucumber/messages'

import { Lineage } from './Lineage'
import Query from './Query'

describe('Query', () => {
Expand Down Expand Up @@ -84,4 +87,89 @@ describe('Query', () => {
assert.deepStrictEqual(cucumberQuery.findAllTestCaseStarted(), testCasesStarted)
})
})

describe('#findLineageBy', () => {
it('returns correct lineage for a minimal scenario', async () => {
const envelopes: ReadonlyArray<Envelope> = (
await fs.readFile(path.join(__dirname, '../../testdata/src/minimal.ndjson'), {
encoding: 'utf-8',
})
)
.split('\n')
.filter((line) => !!line)
.map((line) => JSON.parse(line))
envelopes.forEach((envelope) => cucumberQuery.update(envelope))

const gherkinDocument = envelopes.find((envelope) => envelope.gherkinDocument).gherkinDocument
const feature = gherkinDocument.feature
const scenario = feature.children.find((child) => child.scenario).scenario
const pickle = envelopes.find((envelope) => envelope.pickle).pickle

assert.deepStrictEqual(cucumberQuery.findLineageBy(pickle), {
gherkinDocument,
feature,
scenario,
} satisfies Lineage)
})

it('returns correct lineage for a pickle from an examples table', async () => {
const envelopes: ReadonlyArray<Envelope> = (
await fs.readFile(path.join(__dirname, '../../testdata/src/examples-tables.ndjson'), {
encoding: 'utf-8',
})
)
.split('\n')
.filter((line) => !!line)
.map((line) => JSON.parse(line))
envelopes.forEach((envelope) => cucumberQuery.update(envelope))

const gherkinDocument = envelopes.find((envelope) => envelope.gherkinDocument).gherkinDocument
const feature = gherkinDocument.feature
const scenario = feature.children.find((child) => child.scenario).scenario
const pickle = envelopes.find((envelope) => envelope.pickle).pickle
const examples = scenario.examples[0]
const example = examples.tableBody[0]

assert.deepStrictEqual(cucumberQuery.findLineageBy(pickle), {
gherkinDocument,
feature,
scenario,
examples,
examplesIndex: 0,
example,
exampleIndex: 0,
} satisfies Lineage)
})

it('returns correct lineage for a pickle with background-derived steps', async () => {
const envelopes: ReadonlyArray<Envelope> = (
await fs.readFile(path.join(__dirname, '../../testdata/src/rules-backgrounds.ndjson'), {
encoding: 'utf-8',
})
)
.split('\n')
.filter((line) => !!line)
.map((line) => JSON.parse(line))
envelopes.forEach((envelope) => cucumberQuery.update(envelope))

const gherkinDocument = envelopes.find((envelope) => envelope.gherkinDocument).gherkinDocument
const feature = gherkinDocument.feature
const background = gherkinDocument.feature.children.find(
(child) => child.background
).background
const rule = feature.children.find((child) => child.rule).rule
const ruleBackground = rule.children.find((child) => child.background).background
const scenario = rule.children.find((child) => child.scenario).scenario
const pickle = envelopes.find((envelope) => envelope.pickle).pickle

assert.deepStrictEqual(cucumberQuery.findLineageBy(pickle), {
gherkinDocument,
feature,
background,
rule,
ruleBackground,
scenario,
} satisfies Lineage)
})
})
})
2 changes: 2 additions & 0 deletions javascript/src/Query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export default class Query {
private updateFeature(feature: Feature, lineage: Lineage) {
feature.children.forEach((featureChild) => {
if (featureChild.background) {
lineage.background = featureChild.background
this.updateSteps(featureChild.background.steps)
}
if (featureChild.scenario) {
Expand All @@ -149,6 +150,7 @@ export default class Query {
private updateRule(rule: Rule, lineage: Lineage) {
rule.children.forEach((ruleChild) => {
if (ruleChild.background) {
lineage.ruleBackground = ruleChild.background
this.updateSteps(ruleChild.background.steps)
}
if (ruleChild.scenario) {
Expand Down