Skip to content

Commit

Permalink
Support text/uri-list attachments (#355)
Browse files Browse the repository at this point in the history
Special support for attachments with MIME type text/uri-list, to render as hyperlinks that open in a new tab.

See cucumber/common#2191
  • Loading branch information
davidjgoss authored Aug 2, 2024
1 parent 198a5f0 commit 7c14692
Show file tree
Hide file tree
Showing 10 changed files with 306 additions and 127 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
- Support text/uri-list attachments ([#355](https://github.com/cucumber/react-components/pull/355))

## [22.2.0] - 2024-06-21
### Added
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ This is fine for simple use cases where results are not important.
To render a `<GherkinDocument>` with results and highlighted [Cucumber Expression parameters](https://cucumber.io/docs/cucumber/cucumber-expressions/) parameters it must be nested inside a
[`<Wrapper>`](src/components/app/Wrapper.tsx) component.

## Attachments

Attachments from test runs are shown with their corresponding steps. The baseline behaviour for attachments is a download button. However, we have some special handling for very common MIME types to make them more useful without leaving the report:

- `image/*` - images are rendered with an `<img/>` tag
- `video/*` - videos are rendered with a `<video/` tag
- `text/x.cucumber.log+plain` - logs (from calls to the `log` function in Cucumber) are rendered as monospace text, and support ANSI colors
- `text/uri-list` - one or more URLs are rendered as links that open in a new tab
- `application/json` - JSON is rendered as monospace text and prettified
- `text/*` - other text types are rendered as monospace text

## Styling

The standard styling comes from wrapping your top-level usage with the `CucumberReact` component (sans-props). There are several ways you can apply different styling to the components.
Expand Down
299 changes: 178 additions & 121 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@
"pretty-quick-staged": "pretty-quick --staged"
},
"dependencies": {
"@cucumber/gherkin-utils": "8.0.6",
"@cucumber/messages": "24.0.1",
"@cucumber/query": "12.0.1",
"@cucumber/gherkin-utils": "9.0.0",
"@cucumber/messages": "25.0.1",
"@cucumber/query": "12.2.0",
"@cucumber/tag-expressions": "6.1.0",
"@fortawesome/fontawesome-svg-core": "6.2.1",
"@fortawesome/free-solid-svg-icons": "6.2.1",
Expand Down Expand Up @@ -55,8 +55,8 @@
"react-dom": "~18"
},
"devDependencies": {
"@cucumber/compatibility-kit": "15.0.0",
"@cucumber/fake-cucumber": "16.4.0",
"@cucumber/compatibility-kit": "16.1.0",
"@cucumber/fake-cucumber": "16.5.0",
"@cucumber/gherkin": "28.0.0",
"@cucumber/gherkin-streams": "5.0.1",
"@cucumber/message-streams": "4.0.1",
Expand Down
2 changes: 1 addition & 1 deletion src/components/customise/customRendering.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export interface AttachmentProps {
attachment: messages.Attachment
}

export type AttachmentClasses = Styles<'text' | 'log' | 'icon' | 'image'>
export type AttachmentClasses = Styles<'text' | 'log' | 'icon' | 'image' | 'links'>

export interface BackgroundProps {
background: messages.Background
Expand Down
18 changes: 18 additions & 0 deletions src/components/gherkin/attachment/Attachment.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,21 @@
max-width: 100%;
margin-top: 0.5em;
}

.links {
list-style: none;
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 0;
margin: 0;

a {
text-decoration: none;
color: $anchorColor;
}

svg {
margin-right: 0.5em;
}
}
37 changes: 37 additions & 0 deletions src/components/gherkin/attachment/Attachment.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,4 +166,41 @@ describe('<Attachment>', () => {
'<span style="color:#000">black<span style="color:#AAA">white</span></span>'
)
})

it('renders a link', () => {
const attachment: messages.Attachment = {
mediaType: 'text/uri-list',
contentEncoding: AttachmentContentEncoding.IDENTITY,
body: 'https://cucumber.io',
}

render(<Attachment attachment={attachment} />)

expect(screen.getByRole('link', { name: 'https://cucumber.io' })).to.be.visible
expect(screen.getByRole('link', { name: 'https://cucumber.io' })).to.have.attr(
'href',
'https://cucumber.io'
)
})

it('renders multiple links and ignores blank lines', () => {
const attachment: messages.Attachment = {
mediaType: 'text/uri-list',
contentEncoding: AttachmentContentEncoding.IDENTITY,
body: `https://github.com/cucumber/cucumber-js
https://github.com/cucumber/cucumber-jvm
https://github.com/cucumber/cucumber-ruby
`,
}

render(<Attachment attachment={attachment} />)

expect(screen.getAllByRole('link').length).to.eq(3)
expect(screen.getByRole('link', { name: 'https://github.com/cucumber/cucumber-js' })).to.be
.visible
expect(screen.getByRole('link', { name: 'https://github.com/cucumber/cucumber-jvm' })).to.be
.visible
expect(screen.getByRole('link', { name: 'https://github.com/cucumber/cucumber-ruby' })).to.be
.visible
})
})
20 changes: 20 additions & 0 deletions src/components/gherkin/attachment/Attachment.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,26 @@ Log.args = {
},
} satisfies AttachmentProps

export const Link = Template.bind({})
Link.args = {
attachment: {
mediaType: 'text/uri-list',
contentEncoding: AttachmentContentEncoding.IDENTITY,
body: 'https://cucumber.io',
},
} satisfies AttachmentProps

export const MultipleLinks = Template.bind({})
MultipleLinks.args = {
attachment: {
mediaType: 'text/uri-list',
contentEncoding: AttachmentContentEncoding.IDENTITY,
body: `https://github.com/cucumber/cucumber-js
https://github.com/cucumber/cucumber-jvm
https://github.com/cucumber/cucumber-ruby`,
},
} satisfies AttachmentProps

export const ExternalisedImage = Template.bind({})
ExternalisedImage.storyName = 'Externalised image'
ExternalisedImage.args = {
Expand Down
3 changes: 3 additions & 0 deletions src/components/gherkin/attachment/Attachment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import { ErrorMessage } from '../ErrorMessage.js'
import defaultStyles from './Attachment.module.scss'
import { Image } from './Image.js'
import { Links } from './Links.js'
import { Log } from './Log.js'
import { Text } from './Text.js'
import { Unknown } from './Unknown.js'
Expand All @@ -25,6 +26,8 @@ const DefaultRenderer: DefaultComponent<AttachmentProps, AttachmentClasses> = ({
return <Video attachment={attachment} />
} else if (attachment.mediaType == 'text/x.cucumber.log+plain') {
return <Log attachment={attachment} classes={styles} />
} else if (attachment.mediaType == 'text/uri-list') {
return <Links attachment={attachment} classes={styles} />
} else if (attachment.mediaType.match(/^text\//)) {
return <Text attachment={attachment} classes={styles} />
} else if (attachment.mediaType.match(/^application\/json/)) {
Expand Down
31 changes: 31 additions & 0 deletions src/components/gherkin/attachment/Links.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Attachment } from '@cucumber/messages'
import { faLink } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import React, { FC } from 'react'

import { AttachmentClasses } from '../../customise/index.js'
import { useText } from './useText.js'

export const Links: FC<{
attachment: Attachment
classes: AttachmentClasses
}> = ({ attachment, classes }) => {
const { content } = useText(attachment)
return (
<ul className={classes.links}>
{content
.split('\n')
.filter((line) => !!line)
.map((line, index) => {
return (
<li key={index}>
<a href={line} target="_blank" rel="noreferrer">
<FontAwesomeIcon icon={faLink} />
{line}
</a>
</li>
)
})}
</ul>
)
}

0 comments on commit 7c14692

Please sign in to comment.