Skip to content

Expose UriProvider and refactor context usage #386

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 25, 2025
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 @@ -9,11 +9,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Added
- Add `<CustomRendering/>` to override presentation ([#382](https://github.com/cucumber/react-components/pull/382))
- Add `<ControlledSearchProvider/>`, `<InMemorySearchProvider/>` and `<UrlSearchProvider/>` to provide search state ([#384](https://github.com/cucumber/react-components/pull/384))
- Add `<UriProvider/>`, to provide document URI state ([#386](https://github.com/cucumber/react-components/pull/386))

### Fixed
- Make keyword spacing look right ([#376](https://github.com/cucumber/react-components/pull/376))
- Fix issue with hook steps not being rendered ([#379](https://github.com/cucumber/react-components/pull/379))
- Rounding down of percent passed and adding one decimal place ([#380](https://github.com/cucumber/react-components/pull/380))
- Avoid UUID errors from accordion when document URI contains spaces ([#386](https://github.com/cucumber/react-components/pull/386))

### Changed
- Inherit font-size instead of setting at 14px ([#377](https://github.com/cucumber/react-components/pull/377))
Expand Down
67 changes: 37 additions & 30 deletions src/components/app/GherkinDocumentList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as messages from '@cucumber/messages'
import { getWorstTestStepResult } from '@cucumber/messages'
import { faChevronRight } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import React, { FunctionComponent, useContext, useMemo, useState } from 'react'
import React, { FC, useMemo, useState } from 'react'
import {
Accordion,
AccordionItem,
Expand All @@ -11,26 +11,37 @@ import {
AccordionItemPanel,
} from 'react-accessible-accordion'

import CucumberQueryContext from '../../CucumberQueryContext.js'
import GherkinQueryContext from '../../GherkinQueryContext.js'
import UriContext from '../../UriContext.js'
import { useQueries } from '../../hooks/index.js'
import { GherkinDocument, MDG, StatusIcon } from '../gherkin/index.js'
import styles from './GherkinDocumentList.module.scss'
import { UriProvider } from './UriProvider.js'

interface IProps {
gherkinDocuments?: readonly messages.GherkinDocument[] | null
interface ValidGherkinDocument extends messages.GherkinDocument {
uri: string
}

interface Props {
gherkinDocuments?: readonly messages.GherkinDocument[]
// Set to true if non-PASSED documents should be pre-expanded
preExpand?: boolean
}

export const GherkinDocumentList: FunctionComponent<IProps> = ({ gherkinDocuments, preExpand }) => {
const gherkinQuery = useContext(GherkinQueryContext)
const cucumberQuery = useContext(CucumberQueryContext)
const gherkinDocs = gherkinDocuments || gherkinQuery.getGherkinDocuments()
const gherkinDocumentStatusByUri = useMemo(() => {
const entries: Array<[string, messages.TestStepResultStatus]> = gherkinDocs.map(
const idByUri = new Map<string, string>()
function getIdByUri(uri: string): string {
if (!idByUri.has(uri)) {
idByUri.set(uri, crypto.randomUUID())
}
return idByUri.get(uri) as string
}

export const GherkinDocumentList: FC<Props> = ({ gherkinDocuments, preExpand }) => {
const { gherkinQuery, cucumberQuery } = useQueries()
const documents = (gherkinDocuments || gherkinQuery.getGherkinDocuments()).filter(
(doc) => !!doc.uri
) as ReadonlyArray<ValidGherkinDocument>
const statusByUri = useMemo(() => {
const entries: Array<[string, messages.TestStepResultStatus]> = documents.map(
(gherkinDocument) => {
if (!gherkinDocument.uri) throw new Error('No url for gherkinDocument')
const gherkinDocumentStatus = gherkinDocument.feature
? getWorstTestStepResult(
cucumberQuery.getPickleTestStepResults(gherkinQuery.getPickleIds(gherkinDocument.uri))
Expand All @@ -40,17 +51,13 @@ export const GherkinDocumentList: FunctionComponent<IProps> = ({ gherkinDocument
}
)
return new Map(entries)
}, [gherkinDocs, gherkinQuery, cucumberQuery])
}, [documents, gherkinQuery, cucumberQuery])
const [expanded, setExpanded] = useState<Array<string | number>>(() => {
// Pre-expand any document that is *not* passed - assuming this is what people want to look at first
return preExpand
? (gherkinDocs
.filter(
(doc) =>
doc.uri &&
gherkinDocumentStatusByUri.get(doc.uri) !== messages.TestStepResultStatus.PASSED
)
.map((doc) => doc.uri) as string[])
? (documents
.filter((doc) => statusByUri.get(doc.uri) !== messages.TestStepResultStatus.PASSED)
.map((doc) => getIdByUri(doc.uri)) as string[])
: []
})

Expand All @@ -62,15 +69,15 @@ export const GherkinDocumentList: FunctionComponent<IProps> = ({ gherkinDocument
onChange={setExpanded}
className={styles.accordion}
>
{gherkinDocs.map((doc) => {
if (!doc.uri) throw new Error('No url for gherkinDocument')
const gherkinDocumentStatus = gherkinDocumentStatusByUri.get(doc.uri)
if (!gherkinDocumentStatus) throw new Error(`No status for ${doc.uri}`)
{documents.map((doc) => {
const id = getIdByUri(doc.uri)
const status = statusByUri.get(doc.uri)
if (!status) throw new Error(`No status for ${doc.uri}`)
const source = gherkinQuery.getSource(doc.uri)
if (!source) throw new Error(`No source for ${doc.uri}`)

return (
<AccordionItem key={doc.uri} uuid={doc.uri} className={styles.accordionItem}>
<AccordionItem key={id} uuid={id} className={styles.accordionItem}>
<AccordionItemHeading>
<AccordionItemButton className={styles.accordionButton}>
<FontAwesomeIcon
Expand All @@ -79,20 +86,20 @@ export const GherkinDocumentList: FunctionComponent<IProps> = ({ gherkinDocument
icon={faChevronRight}
/>
<span className={styles.icon}>
<StatusIcon status={gherkinDocumentStatus} />
<StatusIcon status={status} />
</span>
<span>{doc.uri}</span>
</AccordionItemButton>
</AccordionItemHeading>
{expanded.includes(doc.uri) && (
{expanded.includes(id) && (
<AccordionItemPanel className={styles.accordionPanel}>
<UriContext.Provider value={doc.uri}>
<UriProvider uri={doc.uri}>
{source.mediaType === messages.SourceMediaType.TEXT_X_CUCUMBER_GHERKIN_PLAIN ? (
<GherkinDocument gherkinDocument={doc} source={source} />
) : (
<MDG uri={doc.uri}>{source.data}</MDG>
)}
</UriContext.Provider>
</UriProvider>
</AccordionItemPanel>
)}
</AccordionItem>
Expand Down
7 changes: 7 additions & 0 deletions src/components/app/UriProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React, { FC, PropsWithChildren } from 'react'

import UriContext from '../../UriContext.js'

export const UriProvider: FC<PropsWithChildren<{ uri: string }>> = ({ uri, children }) => {
return <UriContext.Provider value={uri}>{children}</UriContext.Provider>
}
1 change: 1 addition & 0 deletions src/components/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export * from './NoMatchResult.js'
export * from './QueriesProvider.js'
export * from './SearchBar.js'
export * from './StatusesSummary.js'
export * from './UriProvider.js'
export * from './UrlSearchProvider.js'