From 6ae6814472780070c7295a6fcc0dd44b6fe0f03b Mon Sep 17 00:00:00 2001 From: David Goss Date: Wed, 17 Jun 2026 17:08:43 +0100 Subject: [PATCH 1/2] Prefer Pickle.Location with fallback to Lineage --- dotnet/Cucumber.Query/Query.cs | 2 + dotnet/Cucumber.QueryTest/QueryTest.cs | 45 ++++++++++++++++ javascript/src/Query.spec.ts | 72 +++++++++++++++----------- javascript/src/Query.ts | 3 ++ 4 files changed, 92 insertions(+), 30 deletions(-) diff --git a/dotnet/Cucumber.Query/Query.cs b/dotnet/Cucumber.Query/Query.cs index fd6b3cd2..531462b3 100644 --- a/dotnet/Cucumber.Query/Query.cs +++ b/dotnet/Cucumber.Query/Query.cs @@ -155,6 +155,8 @@ public IEnumerable FindAttachmentsBy(TestRunHookFinished testRunHook public Location? FindLocationOf(Pickle pickle) { + if (pickle.Location != null) + return pickle.Location; var lineage = FindLineageBy(pickle); if (lineage == null) return null; diff --git a/dotnet/Cucumber.QueryTest/QueryTest.cs b/dotnet/Cucumber.QueryTest/QueryTest.cs index 07b10635..b77742d6 100644 --- a/dotnet/Cucumber.QueryTest/QueryTest.cs +++ b/dotnet/Cucumber.QueryTest/QueryTest.cs @@ -1,5 +1,6 @@ using Io.Cucumber.Messages.Types; using Cucumber.Query; +using System.Text; namespace Cucumber.QueryTest; @@ -48,5 +49,49 @@ public void OmitsTestCaseStartedIfFinishedAndWillBeRetried() Assert.AreEqual(1, _query.TestCasesStartedCount); } + [TestMethod] + public void FindLocationOfFallsBackToScenarioLocationWhenPickleHasNoLocation() + { + var envelopes = LoadEnvelopes("minimal.ndjson"); + var query = BuildQuery(envelopes); + var pickle = FindPickle(envelopes); + + Assert.AreEqual(pickle.Location, query.FindLocationOf(StripLocation(pickle))); + } + + [TestMethod] + public void FindLocationOfFallsBackToExampleRowLocationWhenPickleHasNoLocation() + { + var envelopes = LoadEnvelopes("examples-tables.ndjson"); + var query = BuildQuery(envelopes); + var pickle = FindPickle(envelopes); + + Assert.AreEqual(pickle.Location, query.FindLocationOf(StripLocation(pickle))); + } + + private static Cucumber.Query.Query BuildQuery(IEnumerable envelopes) + { + var repository = Repository.CreateWithAllFeatures(); + foreach (var envelope in envelopes) + repository.Update(envelope); + return new Cucumber.Query.Query(repository); + } + + private static Pickle FindPickle(IEnumerable envelopes) => + envelopes.First(e => e.Pickle != null).Pickle; + + private static Pickle StripLocation(Pickle pickle) => + new Pickle(pickle.Id, pickle.Uri, null, pickle.Name, pickle.Language, pickle.Steps, + pickle.Tags, pickle.AstNodeIds); + + private static IReadOnlyList LoadEnvelopes(string source) + { + var path = Path.Combine(TestFolderHelper.TestFolder, "src", source); + return File.ReadAllLines(path, Encoding.UTF8) + .Where(line => !string.IsNullOrWhiteSpace(line)) + .Select(NdjsonSerializer.Deserialize) + .ToList(); + } + private static string RandomId() => Guid.NewGuid().ToString(); } diff --git a/javascript/src/Query.spec.ts b/javascript/src/Query.spec.ts index d5bbdcbd..778bb1d1 100644 --- a/javascript/src/Query.spec.ts +++ b/javascript/src/Query.spec.ts @@ -90,14 +90,7 @@ describe('Query', () => { describe('#findLineageBy', () => { it('returns correct lineage for a minimal scenario', async () => { - const envelopes: ReadonlyArray = ( - await fs.readFile(path.join(import.meta.dirname, '../../testdata/src/minimal.ndjson'), { - encoding: 'utf-8', - }) - ) - .split('\n') - .filter((line) => !!line) - .map((line) => JSON.parse(line)) + const envelopes = await loadEnvelopes('minimal.ndjson') for (const envelope of envelopes) { cucumberQuery.update(envelope) } @@ -115,17 +108,7 @@ describe('Query', () => { }) it('returns correct lineage for a pickle from an examples table', async () => { - const envelopes: ReadonlyArray = ( - await fs.readFile( - path.join(import.meta.dirname, '../../testdata/src/examples-tables.ndjson'), - { - encoding: 'utf-8', - } - ) - ) - .split('\n') - .filter((line) => !!line) - .map((line) => JSON.parse(line)) + const envelopes = await loadEnvelopes('examples-tables.ndjson') for (const envelope of envelopes) { cucumberQuery.update(envelope) } @@ -149,17 +132,7 @@ describe('Query', () => { }) it('returns correct lineage for a pickle with background-derived steps', async () => { - const envelopes: ReadonlyArray = ( - await fs.readFile( - path.join(import.meta.dirname, '../../testdata/src/rules-backgrounds.ndjson'), - { - encoding: 'utf-8', - } - ) - ) - .split('\n') - .filter((line) => !!line) - .map((line) => JSON.parse(line)) + const envelopes = await loadEnvelopes('rules-backgrounds.ndjson') for (const envelope of envelopes) { cucumberQuery.update(envelope) } @@ -184,4 +157,43 @@ describe('Query', () => { } satisfies Lineage) }) }) + + describe('#findLocationOf', () => { + it('falls back to the scenario location when the pickle has no location', async () => { + const envelopes = await loadEnvelopes('minimal.ndjson') + for (const envelope of envelopes) { + cucumberQuery.update(envelope) + } + const pickle = envelopes.find((envelope) => envelope.pickle).pickle + + assert.deepStrictEqual( + cucumberQuery.findLocationOf({ ...pickle, location: undefined }), + pickle.location + ) + }) + + it('falls back to the example row location when the pickle has no location', async () => { + const envelopes = await loadEnvelopes('examples-tables.ndjson') + for (const envelope of envelopes) { + cucumberQuery.update(envelope) + } + const pickle = envelopes.find((envelope) => envelope.pickle).pickle + + assert.deepStrictEqual( + cucumberQuery.findLocationOf({ ...pickle, location: undefined }), + pickle.location + ) + }) + }) }) + +async function loadEnvelopes(filename: string): Promise> { + return ( + await fs.readFile(path.join(import.meta.dirname, '../../testdata/src', filename), { + encoding: 'utf-8', + }) + ) + .split('\n') + .filter((line) => !!line) + .map((line) => JSON.parse(line)) +} diff --git a/javascript/src/Query.ts b/javascript/src/Query.ts index 2cfb38e2..74038a57 100644 --- a/javascript/src/Query.ts +++ b/javascript/src/Query.ts @@ -443,6 +443,9 @@ export default class Query { } public findLocationOf(pickle: Pickle): Location | undefined { + if (pickle.location) { + return pickle.location + } const lineage = this.findLineageBy(pickle) if (lineage?.example) { return lineage.example.location From c29755d15ac645bf10fa89762dba0a2618f08954 Mon Sep 17 00:00:00 2001 From: David Goss Date: Thu, 18 Jun 2026 08:40:00 +0100 Subject: [PATCH 2/2] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8ec94a6..271676e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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] +### Changed +- Prefer Pickle.Location with fallback to Lineage ([#175](https://github.com/cucumber/query/pull/175)) ## [16.0.0] - 2026-06-11 ### Changed