Skip to content

Commit

Permalink
add ability to download unsupported attachments (#333)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidjgoss authored Jul 13, 2023
1 parent c27b94c commit 48148d4
Show file tree
Hide file tree
Showing 12 changed files with 235 additions and 111 deletions.
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
- Add ability to download unsupported attachments ([#333](https://github.com/cucumber/react-components/pull/333))

## [21.0.1] - 2022-11-26
### Fixed
Expand Down
60 changes: 35 additions & 25 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"elasticlunr": "0.9.5",
"hast-util-sanitize": "3.0.2",
"highlight-words": "1.2.1",
"mime-types": "^2.1.35",
"react-accessible-accordion": "5.0.0",
"react-markdown": "6.0.3",
"rehype-raw": "5.1.0",
Expand All @@ -52,7 +53,7 @@
"react-dom": "~18"
},
"devDependencies": {
"@cucumber/compatibility-kit": "^11.0.0",
"@cucumber/compatibility-kit": "^12.0.0",
"@cucumber/fake-cucumber": "^16.0.0",
"@cucumber/gherkin": "^26.0.0",
"@cucumber/gherkin-streams": "^5.0.1",
Expand All @@ -65,6 +66,7 @@
"@types/glob": "8.0.0",
"@types/jest": "^29.0.0",
"@types/jsdom": "20.0.1",
"@types/mime-types": "^2.1.1",
"@types/node": "18.11.18",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
Expand Down
91 changes: 45 additions & 46 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 from 'react'
import React, { FunctionComponent, useContext, useMemo, useState } from 'react'
import {
Accordion,
AccordionItem,
Expand All @@ -14,9 +14,7 @@ import {
import CucumberQueryContext from '../../CucumberQueryContext'
import GherkinQueryContext from '../../GherkinQueryContext'
import UriContext from '../../UriContext'
import { GherkinDocument } from '../gherkin/GherkinDocument'
import { MDG } from '../gherkin/MDG'
import { StatusIcon } from '../gherkin/StatusIcon'
import { GherkinDocument, MDG, StatusIcon } from '../gherkin'
import styles from './GherkinDocumentList.module.scss'

interface IProps {
Expand All @@ -25,44 +23,43 @@ interface IProps {
preExpand?: boolean
}

export const GherkinDocumentList: React.FunctionComponent<IProps> = ({
gherkinDocuments,
preExpand,
}) => {
const gherkinQuery = React.useContext(GherkinQueryContext)
const cucumberQuery = React.useContext(CucumberQueryContext)

export const GherkinDocumentList: FunctionComponent<IProps> = ({ gherkinDocuments, preExpand }) => {
const gherkinQuery = useContext(GherkinQueryContext)
const cucumberQuery = useContext(CucumberQueryContext)
const gherkinDocs = gherkinDocuments || gherkinQuery.getGherkinDocuments()

const entries: Array<[string, messages.TestStepResultStatus]> = gherkinDocs.map(
(gherkinDocument) => {
if (!gherkinDocument.uri) throw new Error('No url for gherkinDocument')
const gherkinDocumentStatus = gherkinDocument.feature
? getWorstTestStepResult(
cucumberQuery.getPickleTestStepResults(gherkinQuery.getPickleIds(gherkinDocument.uri))
).status
: messages.TestStepResultStatus.UNDEFINED
return [gherkinDocument.uri, gherkinDocumentStatus]
}
)
const gherkinDocumentStatusByUri = new Map(entries)

// Pre-expand any document that is *not* passed - assuming this is what people want to look at first
const preExpanded = preExpand
? (gherkinDocs
.filter(
(doc) =>
doc.uri &&
gherkinDocumentStatusByUri.get(doc.uri) !== messages.TestStepResultStatus.PASSED
)
.map((doc) => doc.uri) as string[])
: []
const gherkinDocumentStatusByUri = useMemo(() => {
const entries: Array<[string, messages.TestStepResultStatus]> = gherkinDocs.map(
(gherkinDocument) => {
if (!gherkinDocument.uri) throw new Error('No url for gherkinDocument')
const gherkinDocumentStatus = gherkinDocument.feature
? getWorstTestStepResult(
cucumberQuery.getPickleTestStepResults(gherkinQuery.getPickleIds(gherkinDocument.uri))
).status
: messages.TestStepResultStatus.UNDEFINED
return [gherkinDocument.uri, gherkinDocumentStatus]
}
)
return new Map(entries)
}, [gherkinDocs, 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[])
: []
})

return (
<Accordion
allowMultipleExpanded={true}
allowZeroExpanded={true}
preExpanded={preExpanded}
preExpanded={expanded}
onChange={setExpanded}
className={styles.accordion}
>
{gherkinDocs.map((doc) => {
Expand All @@ -73,7 +70,7 @@ export const GherkinDocumentList: React.FunctionComponent<IProps> = ({
if (!source) throw new Error(`No source for ${doc.uri}`)

return (
<AccordionItem key={doc.uri} className={styles.accordionItem}>
<AccordionItem key={doc.uri} uuid={doc.uri} className={styles.accordionItem}>
<AccordionItemHeading>
<AccordionItemButton className={styles.accordionButton}>
<FontAwesomeIcon
Expand All @@ -87,15 +84,17 @@ export const GherkinDocumentList: React.FunctionComponent<IProps> = ({
<span>{doc.uri}</span>
</AccordionItemButton>
</AccordionItemHeading>
<AccordionItemPanel className={styles.accordionPanel}>
<UriContext.Provider value={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>
</AccordionItemPanel>
{expanded.includes(doc.uri) && (
<AccordionItemPanel className={styles.accordionPanel}>
<UriContext.Provider value={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>
</AccordionItemPanel>
)}
</AccordionItem>
)
})}
Expand Down
20 changes: 20 additions & 0 deletions src/components/app/NavigationButton.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@import '../../styles/theming';

.navigationButton {
display: flex;
align-items: center;
gap: 4px;
background-color: transparent;
color: $anchorColor;
font-family: inherit;
font-size: inherit;
padding: 0;
border: 0;
margin: 0 0 0.5em 0;
cursor: pointer;

&:hover,
&:focus {
text-decoration: underline;
}
}
11 changes: 11 additions & 0 deletions src/components/app/NavigationButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React, { FC, HTMLAttributes } from 'react'

import styles from './NavigationButton.module.scss'

export const NavigationButton: FC<HTMLAttributes<HTMLButtonElement>> = ({ children, ...props }) => {
return (
<button type="button" {...props} className={styles.navigationButton}>
{children}
</button>
)
}
22 changes: 13 additions & 9 deletions src/components/gherkin/Attachment.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import * as messages from '@cucumber/messages'
import React from 'react'

import { render } from '../../../test-utils'
import { render, screen } from '../../../test-utils'
import { Attachment } from './Attachment'

describe('<Attachment>', () => {
it('renders a download button for a file that isnt video, image or text', () => {
const attachment: messages.Attachment = {
body: 'test content',
mediaType: 'application/pdf',
contentEncoding: messages.AttachmentContentEncoding.IDENTITY,
fileName: 'document.pdf',
}
render(<Attachment attachment={attachment} />)

expect(screen.getByRole('button', { name: 'Download document.pdf' })).toBeVisible()
})

it('renders a video', () => {
const binary = new Uint8Array(10)
binary.fill(255, 0, binary.length)
const attachment: messages.Attachment = {
mediaType: 'video/mp4',
body: 'fake-base64',
Expand All @@ -21,8 +31,6 @@ describe('<Attachment>', () => {
})

it('renders a video with a name', () => {
const binary = new Uint8Array(10)
binary.fill(255, 0, binary.length)
const attachment: messages.Attachment = {
mediaType: 'video/mp4',
fileName: 'the attachment name',
Expand All @@ -37,8 +45,6 @@ describe('<Attachment>', () => {
})

it('renders an image', () => {
const binary = new Uint8Array(10)
binary.fill(255, 0, binary.length)
const attachment: messages.Attachment = {
mediaType: 'image/png',
body: 'fake-base64',
Expand All @@ -52,8 +58,6 @@ describe('<Attachment>', () => {
})

it('renders an image with a name', () => {
const binary = new Uint8Array(10)
binary.fill(255, 0, binary.length)
const attachment: messages.Attachment = {
mediaType: 'image/png',
fileName: 'the attachment name',
Expand Down
Loading

0 comments on commit 48148d4

Please sign in to comment.