diff --git a/CHANGELOG.md b/CHANGELOG.md index 131c82a4..11df3001 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Include duration for each test step ([#396](https://github.com/cucumber/react-components/pull/396)) - Include pass rate in execution summary ([#397](https://github.com/cucumber/react-components/pull/397)) - Add new `` component ([#410](https://github.com/cucumber/react-components/pull/410)) +- Add new `` component and include in report ([#408](https://github.com/cucumber/react-components/pull/408)) ### Changed - Render a more test case-centric report ([#396](https://github.com/cucumber/react-components/pull/396)) diff --git a/src/components/app/Report.tsx b/src/components/app/Report.tsx index f7f5d7d4..7cf3efec 100644 --- a/src/components/app/Report.tsx +++ b/src/components/app/Report.tsx @@ -4,6 +4,7 @@ import { ExecutionSummary } from './ExecutionSummary.js' import { FilteredDocuments } from './FilteredDocuments.js' import styles from './Report.module.scss' import { SearchBar } from './SearchBar.js' +import { TestRunHooks } from './TestRunHooks.js' export const Report: FC = () => { return ( @@ -15,6 +16,9 @@ export const Report: FC = () => {
+
+ +
) } diff --git a/src/components/app/TestRunHooks.module.scss b/src/components/app/TestRunHooks.module.scss new file mode 100644 index 00000000..bc9d5b1c --- /dev/null +++ b/src/components/app/TestRunHooks.module.scss @@ -0,0 +1,16 @@ +.empty { + font-style: italic; +} + +.hooks { + padding: 0; + list-style: none; + + > li { + list-style: none; + + &:not(:empty) { + padding: 0.125em 0; + } + } +} diff --git a/src/components/app/TestRunHooks.stories.tsx b/src/components/app/TestRunHooks.stories.tsx new file mode 100644 index 00000000..ad6e478b --- /dev/null +++ b/src/components/app/TestRunHooks.stories.tsx @@ -0,0 +1,52 @@ +import { Envelope } from '@cucumber/messages' +import { Story } from '@ladle/react' +import React from 'react' + +import emptySample from '../../../acceptance/empty/empty.js' +import globalHooksSample from '../../../acceptance/global-hooks/global-hooks.js' +import globalHooksAfterAllErrorSample from '../../../acceptance/global-hooks-afterall-error/global-hooks-afterall-error.js' +import globalHooksAttachmentsSample from '../../../acceptance/global-hooks-attachments/global-hooks-attachments.js' +import globalHooksBeforeAllErrorSample from '../../../acceptance/global-hooks-beforeall-error/global-hooks-beforeall-error.js' +import { EnvelopesProvider } from './index.js' +import { TestRunHooks } from './TestRunHooks.js' + +type TemplateArgs = { + envelopes: readonly Envelope[] +} + +const Template: Story = ({ envelopes }) => { + return ( + + + + ) +} + +export default { + title: 'App/TestRunHooks', +} + +export const Empty = Template.bind({}) +Empty.args = { + envelopes: emptySample, +} + +export const Default = Template.bind({}) +Default.args = { + envelopes: globalHooksSample, +} + +export const WithAttachments = Template.bind({}) +WithAttachments.args = { + envelopes: globalHooksAttachmentsSample, +} + +export const BeforeAllError = Template.bind({}) +BeforeAllError.args = { + envelopes: globalHooksBeforeAllErrorSample, +} + +export const AfterAllError = Template.bind({}) +AfterAllError.args = { + envelopes: globalHooksAfterAllErrorSample, +} diff --git a/src/components/app/TestRunHooks.tsx b/src/components/app/TestRunHooks.tsx new file mode 100644 index 00000000..65d0f002 --- /dev/null +++ b/src/components/app/TestRunHooks.tsx @@ -0,0 +1,23 @@ +import React, { FC } from 'react' + +import { useTestRunHooks } from '../../hooks/useTestRunHooks.js' +import { TestRunHookOutcome } from '../results/TestRunHookOutcome.js' +import styles from './TestRunHooks.module.scss' + +export const TestRunHooks: FC = () => { + const hooks = useTestRunHooks() + + if (hooks.length === 0) { + return

No test run hooks were executed.

+ } + + return ( +
    + {hooks.map(({ testRunHookFinished, hook }) => { + return ( + + ) + })} +
+ ) +} diff --git a/src/components/app/index.ts b/src/components/app/index.ts index 69014e14..f73e703d 100644 --- a/src/components/app/index.ts +++ b/src/components/app/index.ts @@ -11,4 +11,5 @@ export * from './QueriesProvider.js' export * from './Report.js' export * from './SearchBar.js' export * from './StatusesSummary.js' +export * from './TestRunHooks.js' export * from './UrlSearchProvider.js' diff --git a/src/components/results/TestRunHookOutcome.module.scss b/src/components/results/TestRunHookOutcome.module.scss new file mode 100644 index 00000000..be318e50 --- /dev/null +++ b/src/components/results/TestRunHookOutcome.module.scss @@ -0,0 +1,36 @@ +.header { + display: flex; + width: 100%; + gap: 0.25rem; +} + +.status { + padding-top: 0.1em; +} + +.title { + > * { + vertical-align: middle; + + & + * { + margin-left: 0.5em; + } + } +} + +.name { + font-size: 1em; + display: inline; + font-weight: normal; + padding: 0; + margin: 0; +} + +.content { + margin-left: 1.125rem; + + // bits of detail (doc string, data table, error, attachments) get consistent spacing between them + > * { + margin-top: 0.25rem !important; + } +} diff --git a/src/components/results/TestRunHookOutcome.tsx b/src/components/results/TestRunHookOutcome.tsx new file mode 100644 index 00000000..1c665e62 --- /dev/null +++ b/src/components/results/TestRunHookOutcome.tsx @@ -0,0 +1,35 @@ +import { Hook, HookType, TestRunHookFinished } from '@cucumber/messages' +import React, { FC } from 'react' + +import { StatusIcon } from '../gherkin/index.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 + testRunHookFinished: TestRunHookFinished +} + +export const TestRunHookOutcome: FC = ({ hook, testRunHookFinished }) => { + return ( +
  • +
    +
    + +
    +
    +

    + {hook.name ?? (hook.type === HookType.BEFORE_TEST_RUN ? 'BeforeAll' : 'AfterAll')} +

    + +
    +
    +
    + + +
    +
  • + ) +} diff --git a/src/components/results/TestStepAttachments.tsx b/src/components/results/TestStepAttachments.tsx index 6da6a371..6003718f 100644 --- a/src/components/results/TestStepAttachments.tsx +++ b/src/components/results/TestStepAttachments.tsx @@ -1,4 +1,4 @@ -import { TestStepFinished } from '@cucumber/messages' +import { TestRunHookFinished, TestStepFinished } from '@cucumber/messages' import React from 'react' import { FC } from 'react' @@ -7,12 +7,12 @@ import { Attachment } from '../gherkin/index.js' import styles from './TestStepAttachments.module.scss' interface Props { - testStepFinished: TestStepFinished + testStepOrHookFinished: TestStepFinished | TestRunHookFinished } -export const TestStepAttachments: FC = ({ testStepFinished }) => { +export const TestStepAttachments: FC = ({ testStepOrHookFinished }) => { const { cucumberQuery } = useQueries() - const attachments = cucumberQuery.findAttachmentsBy(testStepFinished) + const attachments = cucumberQuery.findAttachmentsBy(testStepOrHookFinished) return (
      {attachments.map((attachment, index) => { diff --git a/src/components/results/TestStepOutcome.tsx b/src/components/results/TestStepOutcome.tsx index dfbf487e..b8b2c60e 100644 --- a/src/components/results/TestStepOutcome.tsx +++ b/src/components/results/TestStepOutcome.tsx @@ -33,7 +33,7 @@ export const TestStepOutcome: FC = ({ testStep, testStepFinished }) => {
      {testStep.pickleStepId && } - +
      ) diff --git a/src/hooks/helpers.ts b/src/hooks/helpers.ts new file mode 100644 index 00000000..721b9149 --- /dev/null +++ b/src/hooks/helpers.ts @@ -0,0 +1,6 @@ +export function ensure(value: T | undefined, message: string): T { + if (!value) { + throw new Error(message) + } + return value +} diff --git a/src/hooks/useTestRunHooks.ts b/src/hooks/useTestRunHooks.ts new file mode 100644 index 00000000..4b8a298b --- /dev/null +++ b/src/hooks/useTestRunHooks.ts @@ -0,0 +1,24 @@ +import { Hook, TestRunHookFinished } from '@cucumber/messages' + +import { ensure } from './helpers.js' +import { useQueries } from './useQueries.js' + +type RunHooksList = { testRunHookFinished: TestRunHookFinished; hook: Hook }[] + +export function useTestRunHooks(): RunHooksList { + const { cucumberQuery } = useQueries() + const testRunHooksFinished: ReadonlyArray = + cucumberQuery.findAllTestRunHookFinished() + + return testRunHooksFinished.map((testRunHookFinished) => { + const hook = ensure( + cucumberQuery.findHookBy(testRunHookFinished), + 'Expected testRunHookFinished to resolve with a hook' + ) + + return { + testRunHookFinished, + hook, + } + }) +}