Skip to content

Commit

Permalink
feat: Add support for CTA to link a document (#1082)
Browse files Browse the repository at this point in the history
* add support for document cta and open pdfs in browser

* small refactor and clean up tests

* fix axios call to get blob

* bug fixes and linter cleanup

* use var to make it easier to read

* remove unused test

* update unit tests for announcement info

* prevent undefined if error in cms validation

* remove old comment

---------

Co-authored-by: Jacob Capps <99674188+jcbcapps@users.noreply.github.com>
  • Loading branch information
abbyoung and jcbcapps authored Aug 24, 2023
1 parent 6d99151 commit fa0a6bb
Show file tree
Hide file tree
Showing 10 changed files with 266 additions and 69 deletions.
Binary file not shown.
Binary file not shown.
Binary file removed public/uploads/USSF Guardian Commitment Poster.pdf
Binary file not shown.
108 changes: 108 additions & 0 deletions src/__fixtures__/data/cmsAnnouncments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,3 +239,111 @@ export const testAnnouncementWithDeletedArticle = {
status: 'Published',
publishedDate: '2022-05-17T13:44:39.796Z',
}

export const testAnnouncementWithPdfDocument = {
id: 'testAnnouncementDocument',
title: 'Test Announcement Document',
body: {
document: [
{
type: 'paragraph',
children: [
{
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
},
],
},
{
type: 'component-block',
props: {
link: {
value: {
data: {
file: {
url: 'https://test.com/file.pdf',
},
},
},
discriminant: 'document',
},
ctaText: 'Read more',
},
children: [
{
type: 'component-inline-prop',
children: [
{
text: '',
},
],
},
],
component: 'callToAction',
},
{
type: 'paragraph',
children: [
{
text: '',
},
],
},
],
},
status: 'Published',
publishedDate: '2022-05-17T13:44:39.796Z',
}

export const testAnnouncementWithJpgDocument = {
id: 'testAnnouncementJpgDocument',
title: 'Test Announcement Jpg Document',
body: {
document: [
{
type: 'paragraph',
children: [
{
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
},
],
},
{
type: 'component-block',
props: {
link: {
value: {
data: {
file: {
url: 'https://test.com/file.jpg',
},
},
},
discriminant: 'document',
},
ctaText: 'Read more',
},
children: [
{
type: 'component-inline-prop',
children: [
{
text: '',
},
],
},
],
component: 'callToAction',
},
{
type: 'paragraph',
children: [
{
text: '',
},
],
},
],
},
status: 'Published',
publishedDate: '2022-05-17T13:44:39.796Z',
}
56 changes: 54 additions & 2 deletions src/components/AnnouncementInfo/AnnouncementInfo.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,25 @@
* @jest-environment jsdom
*/

import { render, screen } from '@testing-library/react'
import React from 'react'
import { render, screen, fireEvent } from '@testing-library/react'

import React from 'react'
import AnnouncementInfo from './AnnouncementInfo'
import * as openDocumentLink from 'helpers/openDocumentLink'
import {
testAnnouncementWithArticle,
testAnnouncementWithArticleNoSlug,
testAnnouncementWithDeletedArticle,
testAnnouncementWithUrl,
testAnnouncementWithPdfDocument,
testAnnouncementWithJpgDocument,
} from '__fixtures__/data/cmsAnnouncments'

// Spy on the functions that test files and opens PDFs in the browser
// We are not mocking because we want to test isPdf
jest.spyOn(openDocumentLink, 'handleOpenPdfLink').mockImplementation()
jest.spyOn(openDocumentLink, 'isPdf')

describe('AnnouncementInfo component', () => {
test('renders an announcement with a url CTA', () => {
render(<AnnouncementInfo announcement={testAnnouncementWithUrl} />)
Expand Down Expand Up @@ -50,4 +58,48 @@ describe('AnnouncementInfo component', () => {
const link = screen.getByRole('link', { name: 'View deleted article' })
expect(link).toHaveAttribute('href', '/404')
})

test('renders an announcement with a pdf document CTA', async () => {
const pdfString =
testAnnouncementWithPdfDocument.body.document[1].props?.link?.value?.data
?.file?.url

render(<AnnouncementInfo announcement={testAnnouncementWithPdfDocument} />)

expect(screen.getAllByText('Test Announcement Document')).toHaveLength(1)
expect(screen.getAllByText('Read more')).toHaveLength(1)

const link = screen.getByRole('link', { name: 'Read more' })
expect(link).toHaveAttribute('href', pdfString)

fireEvent.click(link)
expect(openDocumentLink.isPdf).toHaveBeenCalledTimes(1)
expect(openDocumentLink.isPdf).toHaveBeenCalledWith(pdfString)
expect(openDocumentLink.handleOpenPdfLink).toHaveBeenCalledTimes(1)
expect(openDocumentLink.handleOpenPdfLink).toHaveBeenCalledWith(pdfString)
})

test('renders an announcement with a non-pdf document CTA', async () => {
jest.resetAllMocks()

const jpgString =
testAnnouncementWithJpgDocument.body.document[1].props?.link?.value?.data
?.file?.url

render(<AnnouncementInfo announcement={testAnnouncementWithJpgDocument} />)

expect(screen.getAllByText('Test Announcement Jpg Document')).toHaveLength(
1
)
expect(screen.getAllByText('Read more')).toHaveLength(1)

const link = screen.getByRole('link', { name: 'Read more' })
expect(link).toHaveAttribute('href', jpgString)

fireEvent.click(link)
expect(openDocumentLink.isPdf).toHaveBeenCalledTimes(1)
expect(openDocumentLink.isPdf).toHaveBeenCalledWith(jpgString)

expect(openDocumentLink.handleOpenPdfLink).toHaveBeenCalledTimes(0)
})
})
26 changes: 25 additions & 1 deletion src/components/AnnouncementInfo/AnnouncementInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React from 'react'
import { DocumentRenderer } from '@keystone-6/document-renderer'
import { InferRenderersForComponentBlocks } from '@keystone-6/fields-document/component-blocks'
import Link from 'next/link'
import styles from './AnnouncementInfo.module.scss'
import { componentBlocks } from 'components/ComponentBlocks/callToAction'
import type { componentBlocks } from 'components/ComponentBlocks/callToAction'
import { AnnouncementRecord } from 'types'
import LinkTo from 'components/util/LinkTo/LinkTo'
import AnnouncementDate from 'components/AnnouncementDate/AnnouncementDate'
import { isPdf, handleOpenPdfLink } from 'helpers/openDocumentLink'

const AnnouncementInfo = ({
announcement,
Expand All @@ -22,6 +24,8 @@ const AnnouncementInfo = ({
typeof componentBlocks
> = {
callToAction: (props: any) => {
const fileUrl = props.link.value.data?.file?.url || ''

return (
<>
{props.link.discriminant === 'article' && (
Expand All @@ -47,6 +51,26 @@ const AnnouncementInfo = ({
{props.ctaText}
</LinkTo>
)}

{props.link.discriminant === 'document' && (
// We need to use the default Next Link component
// with legacyBehavior=false, so we can pass in an onClick
// that will open PDFs in the browser

<Link
legacyBehavior={false}
onClick={(e) => {
if (isPdf(fileUrl)) {
e.preventDefault()
handleOpenPdfLink(fileUrl)
} else return
}}
href={fileUrl}
rel="noreferrer"
className="usa-button">
{props.ctaText}
</Link>
)}
</>
)
},
Expand Down
5 changes: 5 additions & 0 deletions src/components/ComponentBlocks/callToAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ export const componentBlocks = {
listKey: 'Article',
selection: 'id title slug',
}),
document: fields.relationship({
label: 'Document',
listKey: 'Document',
selection: 'id title file { url }',
}),
}
),
},
Expand Down
40 changes: 40 additions & 0 deletions src/helpers/openDocumentLink.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* @jest-environment jsdom
*/
import axios from 'axios'
import { isPdf, handleOpenPdfLink } from './openDocumentLink'

jest.mock('axios', () => ({
get: jest.fn(() => Promise.resolve({ data: { blob: () => 'test' } })),
}))

// Mock URL.createObjectURL
const mockCreateObjectURL = jest.fn()
const mockRevokeObjectURL = jest.fn()
URL.createObjectURL = mockCreateObjectURL
URL.revokeObjectURL = mockRevokeObjectURL

// Mock window.open
const mockWindowOpen = jest.fn()
window.open = mockWindowOpen

describe('isPdf', () => {
test('returns true if url ends with .pdf', () => {
expect(isPdf('https://www.google.com/test.pdf')).toBe(true)
})

test('returns false if url does not end with .pdf', () => {
expect(isPdf('https://www.google.com/test')).toBe(false)
})
})

describe('handleOpenPdfLink', () => {
test('opens a new window if the url is a pdf', async () => {
const pdfString = 'https://www.google.com/test.pdf'
await handleOpenPdfLink(pdfString)
expect(axios.get).toHaveBeenCalled()
expect(mockCreateObjectURL).toHaveBeenCalled()
expect(mockWindowOpen).toHaveBeenCalled()
expect(mockRevokeObjectURL).toHaveBeenCalled()
})
})
19 changes: 19 additions & 0 deletions src/helpers/openDocumentLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import axios from 'axios'

export const isPdf = (url: string) => {
return url.toString().endsWith('.pdf')
}

export const handleOpenPdfLink = async (pdfString: string) => {
// Fetch the file from Keystone / S3
const res = await axios.get(pdfString, { responseType: 'blob' })

// Create a blob from the file and open it in a new tab
const blobData = await res.data
const file = new Blob([blobData], { type: 'application/pdf' })
const fileUrl = URL.createObjectURL(file)

window.open(fileUrl)
// Let the browser know not to keep the reference to the file any longer.
URL.revokeObjectURL(fileUrl)
}
Loading

0 comments on commit fa0a6bb

Please sign in to comment.