Skip to content

add ability to download unsupported attachments #333

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 7 commits into from
Jul 13, 2023
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 @@ -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