From 56f8da5037d83e2725474e2b84a7ec906a9ef785 Mon Sep 17 00:00:00 2001 From: David Goss Date: Tue, 3 Feb 2026 15:43:53 +0000 Subject: [PATCH 1/6] dont show message/exception unless failed --- .../results/TestStepResultDetails.spec.tsx | 15 +++++++++++++++ src/components/results/TestStepResultDetails.tsx | 7 +++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/components/results/TestStepResultDetails.spec.tsx b/src/components/results/TestStepResultDetails.spec.tsx index 60beadf3..fca8d210 100644 --- a/src/components/results/TestStepResultDetails.spec.tsx +++ b/src/components/results/TestStepResultDetails.spec.tsx @@ -62,4 +62,19 @@ describe('TestStepResultDetails', () => { expect(container).to.include.text('at /some/file.js:1:2') }) + + it('should not render the message if the status isnt FAILED', () => { + const { container } = render( + + ) + + expect(container).not.to.include.text('Bad things happened') + }) }) diff --git a/src/components/results/TestStepResultDetails.tsx b/src/components/results/TestStepResultDetails.tsx index 4fe1e290..288f7e5a 100644 --- a/src/components/results/TestStepResultDetails.tsx +++ b/src/components/results/TestStepResultDetails.tsx @@ -1,9 +1,12 @@ -import { TestStepResult } from '@cucumber/messages' +import { TestStepResult, TestStepResultStatus } from '@cucumber/messages' import React, { FC } from 'react' import { ErrorMessage } from '../gherkin/index.js' -export const TestStepResultDetails: FC = ({ message, exception }) => { +export const TestStepResultDetails: FC = ({ status, message, exception }) => { + if (status !== TestStepResultStatus.FAILED) { + return null + } if (exception) { return ( From f6cc60631544150c7328e564a16f86a08f44e227 Mon Sep 17 00:00:00 2001 From: David Goss Date: Tue, 3 Feb 2026 16:15:19 +0000 Subject: [PATCH 2/6] render stackTrace on its own --- src/components/results/TestStepResultDetails.spec.tsx | 9 +++++---- src/components/results/TestStepResultDetails.stories.tsx | 2 +- src/components/results/TestStepResultDetails.tsx | 4 +++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/components/results/TestStepResultDetails.spec.tsx b/src/components/results/TestStepResultDetails.spec.tsx index fca8d210..4efda34b 100644 --- a/src/components/results/TestStepResultDetails.spec.tsx +++ b/src/components/results/TestStepResultDetails.spec.tsx @@ -46,7 +46,7 @@ describe('TestStepResultDetails', () => { expect(container).not.to.include.text('Dont use the legacy field') }) - it('should render a stack trace where present', () => { + it('should prefer a full stack trace where present', () => { const { container } = render( { message="Dont use the legacy field" exception={{ type: 'Whoopsie', - message: 'Bad things happened', - stackTrace: 'at /some/file.js:1:2', + message: 'This bit is superfluous', + stackTrace: 'Whoopsie: Bad things happened\n at /some/file.js:1:2', }} /> ) - expect(container).to.include.text('at /some/file.js:1:2') + expect(container).to.include.text('Whoopsie: Bad things happened\n at /some/file.js:1:2') + expect(container).not.to.include.text('This bit is superfluous') }) it('should not render the message if the status isnt FAILED', () => { diff --git a/src/components/results/TestStepResultDetails.stories.tsx b/src/components/results/TestStepResultDetails.stories.tsx index 20152421..44b5ab3c 100644 --- a/src/components/results/TestStepResultDetails.stories.tsx +++ b/src/components/results/TestStepResultDetails.stories.tsx @@ -54,7 +54,7 @@ WithStackTrace.args = { type: 'TypeError', message: "Cannot read properties of null (reading 'type')", stackTrace: - ' at TodosPage.addItem (/Users/somebody/Projects/my-project/support/pages/TodosPage.ts:39:21)\n at processTicksAndRejections (node:internal/process/task_queues:95:5)\n at CustomWorld. (/Users/somebody/Projects/my-project/support/steps/steps.ts:20:5)', + "TypeError: Cannot read properties of null (reading 'type')\n at TodosPage.addItem (/Users/somebody/Projects/my-project/support/pages/TodosPage.ts:39:21)\n at processTicksAndRejections (node:internal/process/task_queues:95:5)\n at CustomWorld. (/Users/somebody/Projects/my-project/support/steps/steps.ts:20:5)", }, }, } diff --git a/src/components/results/TestStepResultDetails.tsx b/src/components/results/TestStepResultDetails.tsx index 288f7e5a..5061fc21 100644 --- a/src/components/results/TestStepResultDetails.tsx +++ b/src/components/results/TestStepResultDetails.tsx @@ -7,11 +7,13 @@ export const TestStepResultDetails: FC = ({ status, message, exc if (status !== TestStepResultStatus.FAILED) { return null } + if (exception?.stackTrace) { + return {exception.stackTrace} + } if (exception) { return ( {exception.type} {exception.message} - {exception.stackTrace &&
{exception.stackTrace}
}
) } From 80ece45bff467ed6e7a568f5dc143c994740a8fd Mon Sep 17 00:00:00 2001 From: David Goss Date: Tue, 3 Feb 2026 20:01:07 +0000 Subject: [PATCH 3/6] render ambiguous results --- .../results/AmbiguousResult.module.scss | 27 +++++++++++++++++ src/components/results/AmbiguousResult.tsx | 30 +++++++++++++++++++ .../results/SourceReference.module.scss | 10 +++++++ src/components/results/SourceReference.tsx | 20 +++++++++++++ .../results/TestStepOutcome.spec.tsx | 23 +++++++++++++- src/components/results/TestStepOutcome.tsx | 6 +++- 6 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 src/components/results/AmbiguousResult.module.scss create mode 100644 src/components/results/AmbiguousResult.tsx create mode 100644 src/components/results/SourceReference.module.scss create mode 100644 src/components/results/SourceReference.tsx diff --git a/src/components/results/AmbiguousResult.module.scss b/src/components/results/AmbiguousResult.module.scss new file mode 100644 index 00000000..153e370f --- /dev/null +++ b/src/components/results/AmbiguousResult.module.scss @@ -0,0 +1,27 @@ +@use '../../styles/theming'; + +.note { + padding: 0; + margin: 0; +} + +.definitions { + padding: 0; + margin: 0; + list-style: none; + + > * + * { + margin-top: 0.25em; + } +} + +.definition { + > * + * { + margin-left: 0.5em; + } +} + +.pattern { + color: theming.$parameterColor; + font-size: inherit; +} \ No newline at end of file diff --git a/src/components/results/AmbiguousResult.tsx b/src/components/results/AmbiguousResult.tsx new file mode 100644 index 00000000..f0f8564c --- /dev/null +++ b/src/components/results/AmbiguousResult.tsx @@ -0,0 +1,30 @@ +import { TestStep } from '@cucumber/messages' +import React, { FC } from 'react' + +import { useQueries } from '../../hooks/index.js' +import styles from './AmbiguousResult.module.scss' +import { SourceReference } from './SourceReference.js' + +interface Props { + testStep: TestStep +} + +export const AmbiguousResult: FC = ({ testStep }) => { + const { cucumberQuery } = useQueries() + const stepDefinitions = cucumberQuery.findStepDefinitionsBy(testStep) + return ( + <> +

+ Multiple matching step definitions found: +

+
    + {stepDefinitions.map((stepDefinition) => ( +
  • + {stepDefinition.pattern.source} + +
  • + ))} +
+ + ) +} diff --git a/src/components/results/SourceReference.module.scss b/src/components/results/SourceReference.module.scss new file mode 100644 index 00000000..38f30da1 --- /dev/null +++ b/src/components/results/SourceReference.module.scss @@ -0,0 +1,10 @@ +@use '../../styles/theming'; + +.sourceReference { + background-color: theming.$codeBackgroundColor; + color: theming.$codeTextColor; + font-size: 0.75em; + padding: 0.1em 0.2em; + border: 1px solid theming.$panelAccentColor; + border-radius: 0.25em; +} \ No newline at end of file diff --git a/src/components/results/SourceReference.tsx b/src/components/results/SourceReference.tsx new file mode 100644 index 00000000..2d9caa6b --- /dev/null +++ b/src/components/results/SourceReference.tsx @@ -0,0 +1,20 @@ +import { SourceReference as MessagesSourceReference } from '@cucumber/messages' +import React from 'react' +import { FC } from 'react' + +import styles from './SourceReference.module.scss' + +interface Props { + sourceReference: MessagesSourceReference +} + +export const SourceReference: FC = ({sourceReference}) => { + if (sourceReference.uri) { + let stringified = sourceReference.uri + if (sourceReference.location) { + stringified += `:${sourceReference.location.line}` + } + return {stringified} + } + return null +} \ No newline at end of file diff --git a/src/components/results/TestStepOutcome.spec.tsx b/src/components/results/TestStepOutcome.spec.tsx index 4dcdc0c5..bf5ff036 100644 --- a/src/components/results/TestStepOutcome.spec.tsx +++ b/src/components/results/TestStepOutcome.spec.tsx @@ -4,13 +4,34 @@ import { render } from '@testing-library/react' import { expect } from 'chai' import React from 'react' +import ambiguousSample from '../../../acceptance/ambiguous/ambiguous.js' import minimalSample from '../../../acceptance/minimal/minimal.js' import { EnvelopesProvider } from '../app/index.js' import { TestStepOutcome } from './TestStepOutcome.js' describe('TestStepOutcome', () => { + it('should show ambiguous step definitions with source references for an ambiguous result', () => { + const cucumberQuery = new CucumberQuery() + ambiguousSample.forEach((envelope) => cucumberQuery.update(envelope)) + + const [testCaseStarted] = cucumberQuery.findAllTestCaseStarted() + const [[testStepFinished, testStep]] = + cucumberQuery.findTestStepFinishedAndTestStepBy(testCaseStarted) + + const { getByText } = render( + + + + ) + + expect(getByText('Multiple matching step definitions found:')).to.be.visible + expect(getByText('^a (.*?) with (.*?)$')).to.be.visible + expect(getByText('samples/ambiguous/ambiguous.ts:3')).to.be.visible + expect(getByText('^a step with (.*?)$')).to.be.visible + expect(getByText('samples/ambiguous/ambiguous.ts:7')).to.be.visible + }) + it('should still work when we cant resolve the original step', () => { - // omit children from gherkinDocument.feature so that Step is unresolved const envelopes = minimalSample.map((envelope) => { if (envelope.gherkinDocument) { return { diff --git a/src/components/results/TestStepOutcome.tsx b/src/components/results/TestStepOutcome.tsx index 9290cfeb..ee04ef3a 100644 --- a/src/components/results/TestStepOutcome.tsx +++ b/src/components/results/TestStepOutcome.tsx @@ -1,10 +1,11 @@ -import { PickleStep, TestStep, TestStepFinished } from '@cucumber/messages' +import { PickleStep, TestStep, TestStepFinished, TestStepResultStatus } from '@cucumber/messages' import React, { FC } from 'react' import { useQueries } from '../../hooks/index.js' import { composeHookStepTitle } from '../gherkin/composeHookStepTitle.js' import { composePickleStepTitle } from '../gherkin/composePickleStepTitle.js' import { DataTable, DocString, Keyword, Parameter, StatusIcon } from '../gherkin/index.js' +import { AmbiguousResult } from './AmbiguousResult.js' import { TestStepAttachments } from './TestStepAttachments.js' import { TestStepDuration } from './TestStepDuration.js' import styles from './TestStepOutcome.module.scss' @@ -32,6 +33,9 @@ export const TestStepOutcome: FC = ({ testStep, testStepFinished }) => {
{testStep.pickleStepId && } + {testStepFinished.testStepResult.status === TestStepResultStatus.AMBIGUOUS && ( + + )}
From 38c8e876c0f22e46452034153fffbbb8d73d5942 Mon Sep 17 00:00:00 2001 From: David Goss Date: Tue, 3 Feb 2026 20:15:17 +0000 Subject: [PATCH 4/6] roll out pattern to failed result --- .../results/AmbiguousResult.module.scss | 2 +- src/components/results/FailedResult.spec.tsx | 74 +++++++++++++++++ ...s.stories.tsx => FailedResult.stories.tsx} | 6 +- ...StepResultDetails.tsx => FailedResult.tsx} | 11 +-- .../results/SourceReference.module.scss | 2 +- src/components/results/SourceReference.tsx | 4 +- src/components/results/TestRunHookOutcome.tsx | 8 +- src/components/results/TestStepOutcome.tsx | 6 +- .../results/TestStepResultDetails.spec.tsx | 81 ------------------- src/components/results/index.ts | 2 +- 10 files changed, 97 insertions(+), 99 deletions(-) create mode 100644 src/components/results/FailedResult.spec.tsx rename src/components/results/{TestStepResultDetails.stories.tsx => FailedResult.stories.tsx} (93%) rename src/components/results/{TestStepResultDetails.tsx => FailedResult.tsx} (63%) delete mode 100644 src/components/results/TestStepResultDetails.spec.tsx diff --git a/src/components/results/AmbiguousResult.module.scss b/src/components/results/AmbiguousResult.module.scss index 153e370f..a760851d 100644 --- a/src/components/results/AmbiguousResult.module.scss +++ b/src/components/results/AmbiguousResult.module.scss @@ -24,4 +24,4 @@ .pattern { color: theming.$parameterColor; font-size: inherit; -} \ No newline at end of file +} diff --git a/src/components/results/FailedResult.spec.tsx b/src/components/results/FailedResult.spec.tsx new file mode 100644 index 00000000..2e24419d --- /dev/null +++ b/src/components/results/FailedResult.spec.tsx @@ -0,0 +1,74 @@ +import { TestStepResultStatus } from '@cucumber/messages' +import { render } from '@testing-library/react' +import { expect } from 'chai' +import React from 'react' + +import { FailedResult } from './FailedResult.js' + +describe('FailedResult', () => { + it('should render nothing if no message or exception', () => { + const { container } = render( + + ) + + expect(container).to.be.empty + }) + + it('should render the message for a legacy message', () => { + const { container } = render( + + ) + + expect(container).to.include.text('Oh no a bad thing happened') + }) + + it('should render the message for a typed exception', () => { + const { container } = render( + + ) + + expect(container).to.include.text('Whoopsie Bad things happened') + expect(container).not.to.include.text('Dont use the legacy field') + }) + + it('should prefer a full stack trace where present', () => { + const { container } = render( + + ) + + expect(container).to.include.text('Whoopsie: Bad things happened\n at /some/file.js:1:2') + expect(container).not.to.include.text('This bit is superfluous') + }) +}) diff --git a/src/components/results/TestStepResultDetails.stories.tsx b/src/components/results/FailedResult.stories.tsx similarity index 93% rename from src/components/results/TestStepResultDetails.stories.tsx rename to src/components/results/FailedResult.stories.tsx index 44b5ab3c..c2091242 100644 --- a/src/components/results/TestStepResultDetails.stories.tsx +++ b/src/components/results/FailedResult.stories.tsx @@ -2,10 +2,10 @@ import { TestStepResult, TestStepResultStatus } from '@cucumber/messages' import { Story } from '@ladle/react' import React from 'react' -import { TestStepResultDetails } from './TestStepResultDetails.js' +import { FailedResult } from './FailedResult.js' export default { - title: 'Results/TestStepResultDetails', + title: 'Results/FailedResult', } type TemplateArgs = { @@ -13,7 +13,7 @@ type TemplateArgs = { } const Template: Story = ({ result }) => { - return + return } export const Legacy = Template.bind({}) diff --git a/src/components/results/TestStepResultDetails.tsx b/src/components/results/FailedResult.tsx similarity index 63% rename from src/components/results/TestStepResultDetails.tsx rename to src/components/results/FailedResult.tsx index 5061fc21..2d0b4e10 100644 --- a/src/components/results/TestStepResultDetails.tsx +++ b/src/components/results/FailedResult.tsx @@ -1,12 +1,13 @@ -import { TestStepResult, TestStepResultStatus } from '@cucumber/messages' +import { TestStepResult } from '@cucumber/messages' import React, { FC } from 'react' import { ErrorMessage } from '../gherkin/index.js' -export const TestStepResultDetails: FC = ({ status, message, exception }) => { - if (status !== TestStepResultStatus.FAILED) { - return null - } +interface Props { + result: TestStepResult +} + +export const FailedResult: FC = ({ result: { exception, message } }) => { if (exception?.stackTrace) { return {exception.stackTrace} } diff --git a/src/components/results/SourceReference.module.scss b/src/components/results/SourceReference.module.scss index 38f30da1..7357e120 100644 --- a/src/components/results/SourceReference.module.scss +++ b/src/components/results/SourceReference.module.scss @@ -7,4 +7,4 @@ padding: 0.1em 0.2em; border: 1px solid theming.$panelAccentColor; border-radius: 0.25em; -} \ No newline at end of file +} diff --git a/src/components/results/SourceReference.tsx b/src/components/results/SourceReference.tsx index 2d9caa6b..e5740421 100644 --- a/src/components/results/SourceReference.tsx +++ b/src/components/results/SourceReference.tsx @@ -8,7 +8,7 @@ interface Props { sourceReference: MessagesSourceReference } -export const SourceReference: FC = ({sourceReference}) => { +export const SourceReference: FC = ({ sourceReference }) => { if (sourceReference.uri) { let stringified = sourceReference.uri if (sourceReference.location) { @@ -17,4 +17,4 @@ export const SourceReference: FC = ({sourceReference}) => { return {stringified} } return null -} \ No newline at end of file +} diff --git a/src/components/results/TestRunHookOutcome.tsx b/src/components/results/TestRunHookOutcome.tsx index 1c665e62..86e52532 100644 --- a/src/components/results/TestRunHookOutcome.tsx +++ b/src/components/results/TestRunHookOutcome.tsx @@ -1,11 +1,11 @@ -import { Hook, HookType, TestRunHookFinished } from '@cucumber/messages' +import { Hook, HookType, TestRunHookFinished, TestStepResultStatus } from '@cucumber/messages' import React, { FC } from 'react' import { StatusIcon } from '../gherkin/index.js' +import { FailedResult } from './FailedResult.js' import styles from './TestRunHookOutcome.module.scss' import { TestStepAttachments } from './TestStepAttachments.js' import { TestStepDuration } from './TestStepDuration.js' -import { TestStepResultDetails } from './TestStepResultDetails.js' interface Props { hook: Hook @@ -27,7 +27,9 @@ export const TestRunHookOutcome: FC = ({ hook, testRunHookFinished }) =>
- + {testRunHookFinished.result.status === TestStepResultStatus.FAILED && ( + + )}
diff --git a/src/components/results/TestStepOutcome.tsx b/src/components/results/TestStepOutcome.tsx index ee04ef3a..6682167f 100644 --- a/src/components/results/TestStepOutcome.tsx +++ b/src/components/results/TestStepOutcome.tsx @@ -6,10 +6,10 @@ import { composeHookStepTitle } from '../gherkin/composeHookStepTitle.js' import { composePickleStepTitle } from '../gherkin/composePickleStepTitle.js' import { DataTable, DocString, Keyword, Parameter, StatusIcon } from '../gherkin/index.js' import { AmbiguousResult } from './AmbiguousResult.js' +import { FailedResult } from './FailedResult.js' import { TestStepAttachments } from './TestStepAttachments.js' import { TestStepDuration } from './TestStepDuration.js' import styles from './TestStepOutcome.module.scss' -import { TestStepResultDetails } from './TestStepResultDetails.js' interface Props { testStep: TestStep @@ -36,7 +36,9 @@ export const TestStepOutcome: FC = ({ testStep, testStepFinished }) => { {testStepFinished.testStepResult.status === TestStepResultStatus.AMBIGUOUS && ( )} - + {testStepFinished.testStepResult.status === TestStepResultStatus.FAILED && ( + + )} diff --git a/src/components/results/TestStepResultDetails.spec.tsx b/src/components/results/TestStepResultDetails.spec.tsx deleted file mode 100644 index 4efda34b..00000000 --- a/src/components/results/TestStepResultDetails.spec.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { TestStepResultStatus } from '@cucumber/messages' -import { render } from '@testing-library/react' -import { expect } from 'chai' -import React from 'react' - -import { TestStepResultDetails } from './TestStepResultDetails.js' - -describe('TestStepResultDetails', () => { - it('should render nothing if no message or exception', () => { - const { container } = render( - - ) - - expect(container).to.be.empty - }) - - it('should render the message for a legacy message', () => { - const { container } = render( - - ) - - expect(container).to.include.text('Oh no a bad thing happened') - }) - - it('should render the message for a typed exception', () => { - const { container } = render( - - ) - - expect(container).to.include.text('Whoopsie Bad things happened') - expect(container).not.to.include.text('Dont use the legacy field') - }) - - it('should prefer a full stack trace where present', () => { - const { container } = render( - - ) - - expect(container).to.include.text('Whoopsie: Bad things happened\n at /some/file.js:1:2') - expect(container).not.to.include.text('This bit is superfluous') - }) - - it('should not render the message if the status isnt FAILED', () => { - const { container } = render( - - ) - - expect(container).not.to.include.text('Bad things happened') - }) -}) diff --git a/src/components/results/index.ts b/src/components/results/index.ts index 04c79995..3708bec0 100644 --- a/src/components/results/index.ts +++ b/src/components/results/index.ts @@ -1,2 +1,2 @@ +export * from './FailedResult.js' export * from './TestCaseOutcome.js' -export * from './TestStepResultDetails.js' From d50fa6bc943ca9d118a1b9278807472daecece24 Mon Sep 17 00:00:00 2001 From: David Goss Date: Wed, 4 Feb 2026 00:09:43 +0000 Subject: [PATCH 5/6] render undefined result with snippets --- .../results/TestStepOutcome.spec.tsx | 39 +++++++++++++++++++ src/components/results/TestStepOutcome.tsx | 4 ++ .../results/UndefinedResult.module.scss | 18 +++++++++ src/components/results/UndefinedResult.tsx | 39 +++++++++++++++++++ 4 files changed, 100 insertions(+) create mode 100644 src/components/results/UndefinedResult.module.scss create mode 100644 src/components/results/UndefinedResult.tsx diff --git a/src/components/results/TestStepOutcome.spec.tsx b/src/components/results/TestStepOutcome.spec.tsx index bf5ff036..f1e8d1c1 100644 --- a/src/components/results/TestStepOutcome.spec.tsx +++ b/src/components/results/TestStepOutcome.spec.tsx @@ -6,6 +6,7 @@ import React from 'react' import ambiguousSample from '../../../acceptance/ambiguous/ambiguous.js' import minimalSample from '../../../acceptance/minimal/minimal.js' +import undefinedSample from '../../../acceptance/undefined/undefined.js' import { EnvelopesProvider } from '../app/index.js' import { TestStepOutcome } from './TestStepOutcome.js' @@ -31,6 +32,44 @@ describe('TestStepOutcome', () => { expect(getByText('samples/ambiguous/ambiguous.ts:7')).to.be.visible }) + it('should show snippets for an undefined result when available', () => { + const cucumberQuery = new CucumberQuery() + undefinedSample.forEach((envelope) => cucumberQuery.update(envelope)) + + const [testCaseStarted] = cucumberQuery.findAllTestCaseStarted() + const [[testStepFinished, testStep]] = + cucumberQuery.findTestStepFinishedAndTestStepBy(testCaseStarted) + + const { container, getByText } = render( + + + + ) + + expect(getByText('No step definition found. Implement with the snippet(s) below:')).to.be + .visible + expect(container).to.include.text('Given("a step that is yet to be defined", ()') + }) + + it('should show a brief note for an undefined result when no snippets available', () => { + const cucumberQuery = new CucumberQuery() + // omit suggestion messages so there are no snippets + const envelopes = undefinedSample.filter((envelope) => !envelope.suggestion) + envelopes.forEach((envelope) => cucumberQuery.update(envelope)) + + const [testCaseStarted] = cucumberQuery.findAllTestCaseStarted() + const [[testStepFinished, testStep]] = + cucumberQuery.findTestStepFinishedAndTestStepBy(testCaseStarted) + + const { getByText } = render( + + + + ) + + expect(getByText('No step definition found.')).to.be.visible + }) + it('should still work when we cant resolve the original step', () => { const envelopes = minimalSample.map((envelope) => { if (envelope.gherkinDocument) { diff --git a/src/components/results/TestStepOutcome.tsx b/src/components/results/TestStepOutcome.tsx index 6682167f..dc46b32c 100644 --- a/src/components/results/TestStepOutcome.tsx +++ b/src/components/results/TestStepOutcome.tsx @@ -10,6 +10,7 @@ import { FailedResult } from './FailedResult.js' import { TestStepAttachments } from './TestStepAttachments.js' import { TestStepDuration } from './TestStepDuration.js' import styles from './TestStepOutcome.module.scss' +import { UndefinedResult } from './UndefinedResult.js' interface Props { testStep: TestStep @@ -39,6 +40,9 @@ export const TestStepOutcome: FC = ({ testStep, testStepFinished }) => { {testStepFinished.testStepResult.status === TestStepResultStatus.FAILED && ( )} + {testStepFinished.testStepResult.status === TestStepResultStatus.UNDEFINED && ( + + )} diff --git a/src/components/results/UndefinedResult.module.scss b/src/components/results/UndefinedResult.module.scss new file mode 100644 index 00000000..bfdad7d2 --- /dev/null +++ b/src/components/results/UndefinedResult.module.scss @@ -0,0 +1,18 @@ +@use '../../styles/theming'; + +.note { + padding: 0; + margin: 0; +} + +.snippets { + position: relative; + white-space: pre-wrap; + font-size: 0.875em; + padding: 0.666em 0.75em; + border-radius: 0.25em; + margin: 0; + overflow-x: auto; + background-color: theming.$codeBackgroundColor; + color: theming.$codeTextColor; +} diff --git a/src/components/results/UndefinedResult.tsx b/src/components/results/UndefinedResult.tsx new file mode 100644 index 00000000..f15515b7 --- /dev/null +++ b/src/components/results/UndefinedResult.tsx @@ -0,0 +1,39 @@ +import { TestStep } from '@cucumber/messages' +import React, { FC } from 'react' + +import { ensure } from '../../hooks/helpers.js' +import { useQueries } from '../../hooks/index.js' +import styles from './UndefinedResult.module.scss' + +interface Props { + testStep: TestStep +} + +export const UndefinedResult: FC = ({ testStep }) => { + const { cucumberQuery } = useQueries() + const pickleStep = ensure( + cucumberQuery.findPickleStepBy(testStep), + 'Expected TestStep with UNDEFINED status to have a PickleStep' + ) + const snippets = cucumberQuery + .findSuggestionsBy(pickleStep) + .flatMap((suggestion) => suggestion.snippets) + if (snippets.length === 0) { + return ( +

+ No step definition found. +

+ ) + } + const concatenatedCode = snippets.map((snippet) => snippet.code).join('\n\n') + return ( + <> +

+ No step definition found. Implement with the snippet(s) below: +

+
+        {concatenatedCode}
+      
+ + ) +} From e9170d8dea144cfcc3d80db7d23cac4b9ee604e7 Mon Sep 17 00:00:00 2001 From: David Goss Date: Thu, 5 Feb 2026 17:25:03 +0000 Subject: [PATCH 6/6] avoid unnecessary extra width in snippets --- src/components/results/UndefinedResult.module.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/results/UndefinedResult.module.scss b/src/components/results/UndefinedResult.module.scss index bfdad7d2..e7b13df3 100644 --- a/src/components/results/UndefinedResult.module.scss +++ b/src/components/results/UndefinedResult.module.scss @@ -8,6 +8,7 @@ .snippets { position: relative; white-space: pre-wrap; + width: fit-content; font-size: 0.875em; padding: 0.666em 0.75em; border-radius: 0.25em;