Skip to content
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

PORTALS-3308 - What's in Portals Component (GoalsV2) #1399

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
6d2ccdb
Replace the Goals component with a copy, GoalsV2
afwillia Nov 14, 2024
6714f25
Update copies of Goals.Desktop, Goals.Mobile and index.ts for GoalsV2
afwillia Nov 14, 2024
3ba91fc
Copy Goals CSS as GoalsV2
afwillia Nov 14, 2024
50668f9
Add a box surrounding the GoalsV2Desktop panels
afwillia Nov 14, 2024
ca758c0
rearrange individual goal panels to match design
afwillia Nov 14, 2024
513fd94
Tweak styling of individual goal panels
afwillia Nov 14, 2024
0c25ce9
Format the box containing the goal cards
afwillia Nov 15, 2024
f2b888c
Turn each item into a Card
afwillia Nov 20, 2024
643f34b
Format the new box that contains the card links
afwillia Nov 20, 2024
f292993
add GoalsV2.stories.tsx
afwillia Nov 20, 2024
c68fb26
copy storybook from Goals
afwillia Nov 20, 2024
c845ed6
use the elite portal in the goalsV2 story
afwillia Nov 20, 2024
dfc72d9
merge main updates with react router v6
afwillia Nov 20, 2024
5eed0da
Add a unit test file for the individual cards
afwillia Nov 21, 2024
a089d30
Add unit tests for the GoalsV2 box
afwillia Nov 21, 2024
75c5cd0
Remove unnecessary mock access token from test
afwillia Nov 22, 2024
001f516
Merge branch 'main' into PORTALS-3308
afwillia Nov 22, 2024
7073b49
Merge branch 'main' into PORTALS-3308
afwillia Dec 2, 2024
3cc4331
Merge branch 'main' into PORTALS-3308
afwillia Dec 3, 2024
d795916
WIP: address Nick's comments in GoalsV2.
afwillia Dec 4, 2024
33e2dea
update goalsV2 tests
afwillia Dec 4, 2024
f68b999
change main box to Goals class instead of GoalsV2
afwillia Dec 4, 2024
20c7954
Move the GoalsV2DataProps into an array outside of the return statement
afwillia Dec 4, 2024
39b26c8
add space between header and button and round its corners.
afwillia Dec 5, 2024
a2e5844
Make the button link a prop
afwillia Dec 5, 2024
71c3d30
update tests with dataLink
afwillia Dec 5, 2024
430e34a
Add dataLink to stories
afwillia Dec 5, 2024
bef55b6
remove the goalsV2 scss file and just use MUI
afwillia Dec 5, 2024
abd1401
remove _goalsV2 from _all.scss
afwillia Dec 6, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react'
import { render, screen, fireEvent } from '@testing-library/react'
import '@testing-library/jest-dom'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import GoalsV2Desktop from './GoalsV2.Desktop'

const mockProps = {
asset: 'https://example.com/asset.jpg',
link: 'https://example.com',
countSql: 'SELECT COUNT(*) FROM syn12345',
title: 'Test Title',
summary: 'Test Summary',
}

const queryClient = new QueryClient()

describe('GoalsV2Desktop', () => {
test('renders the component with the correct props', () => {
render(
<QueryClientProvider client={queryClient}>
<GoalsV2Desktop {...mockProps} />
</QueryClientProvider>,
)

const image = screen.getByRole('img')
expect(image).toHaveAttribute('src', mockProps.asset)
expect(image).toHaveAttribute('alt', mockProps.title)

const title = screen.getByText(mockProps.title)
expect(title).toBeInTheDocument()
})

test('opens the link when the card is clicked', () => {
window.open = jest.fn()

render(
<QueryClientProvider client={queryClient}>
<GoalsV2Desktop {...mockProps} />
</QueryClientProvider>,
)

const card = screen.getByRole('button', { name: /Test Title/i })
fireEvent.click(card)

expect(window.open).toHaveBeenCalledWith(mockProps.link)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react'
import { GoalsV2DataProps } from './GoalsV2'
import QueryCount from '../QueryCount/QueryCount'
import IconButton from '@mui/material/IconButton'
import NavigateNextIcon from '@mui/icons-material/NavigateNext'
import { Typography } from '@mui/material'
import { Card, CardActionArea, CardContent, CardMedia } from '@mui/material'

export default function GoalsV2Desktop({
asset,
link,
countSql,
title,
}: GoalsV2DataProps) {
return (
<Card
sx={{
width: 200,
height: 'auto',
backgroundColor: 'transparent',
borderColor: 'transparent',
boxShadow: 'none',
}}
>
<CardActionArea onClick={() => window.open(link)}>
<CardMedia
component="img"
sx={{ height: 150, width: '100%', paddingX: 2, overflow: 'visible' }}
image={asset}
alt={title}
/>
<CardContent
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Typography variant="h6" component="strong" sx={{ marginRight: 1 }}>
{countSql && (
<QueryCount parens={false} query={{ sql: countSql }} />
)}
</Typography>
<Typography variant="body1">{title}</Typography>
<IconButton sx={{ color: '#5BA998' }}>
<NavigateNextIcon />
</IconButton>
</CardContent>
</CardActionArea>
</Card>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react'
import { GoalsV2DataProps } from './GoalsV2'
import ExpandableContent from '../home_page/ExpandableContent'
import QueryCount from '../QueryCount/QueryCount'
import { Button } from '@mui/material'

export default function GoalsV2Mobile({
link,
summary,
countSql,
title,
}: GoalsV2DataProps) {
const titleElement = (
<div className="GoalsV2__Mobile__Header">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Go ahead and remove all classNames that are not being used for styling

{countSql && (
<span className="GoalsV2__Mobile__Header__Count">
<QueryCount parens={false} query={{ sql: countSql }} />
</span>
)}
<span className="GoalsV2__Mobile__Header__Title"> {title} </span>
</div>
)
const content = (
<div className="GoalsV2__Mobile__Content">
<p>{summary}</p>
<Button
variant="contained"
color="secondary"
className="GoalsV2__Mobile__Content__Link"
href={link}
>
Explore
</Button>
</div>
)
return <ExpandableContent title={titleElement} content={content} />
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Meta, StoryObj } from '@storybook/react'
import Goals from './GoalsV2'

const meta = {
title: 'Home Page/GoalsV2',
component: Goals,
parameters: {
chromatic: { viewports: [600, 1200] },
},
} satisfies Meta
export default meta
type Story = StoryObj<typeof meta>

export const Demo: Story = {
args: {
entityId: 'syn22315959',
dataLink: 'https://eliteportal.synapse.org/Explore/Data',
},
}
128 changes: 128 additions & 0 deletions packages/synapse-react-client/src/components/GoalsV2/GoalsV2.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import React from 'react'
import { render, screen, waitFor } from '@testing-library/react'
import '@testing-library/jest-dom'
import { rest } from 'msw'
import { setupServer } from 'msw/node'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import GoalsV2 from './GoalsV2'
import {
SynapseContextProvider,
SynapseContextType,
} from '../../utils/context/SynapseContext'

const mockSynapseContext: Partial<SynapseContextType> = {
isInExperimentalMode: false,
utcTime: false,
withErrorBoundary: false,
downloadCartPageUrl: '/DownloadCart',
appId: undefined,
}

const queryClient = new QueryClient()

const server = setupServer(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to call setupServer, we do that already. You can import server from '../mocks/msw/server'

rest.post(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your handlers look good, but we have a utility generateAsyncJobHandlers that can abstract away setting up the async/start and async/get handlers that might reduce the amount of code here. Optionally consider using that here

'https://repo-prod.prod.sagebase.org/repo/v1/entity/:entityId/table/query/async/start',
(req, res, ctx) => {
return res(
ctx.json<{ token: string }>({
token: 'mockToken',
}),
)
},
),
rest.get(
'https://repo-prod.prod.sagebase.org/repo/v1/entity/:entityId/table/query/async/get/:token',
(req, res, ctx) => {
return res(
ctx.json({
queryResult: {
queryResults: {
rows: [
{
values: [
'syn22315959',
'SELECT COUNT(*) FROM syn22315959',
'Sample Title',
'Sample Summary',
'https://example.com',
'12345',
],
},
],
},
},
}),
)
},
),
)

beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())

describe('GoalsV2', () => {
test('renders the component with provided props', async () => {
render(
<QueryClientProvider client={queryClient}>
<SynapseContextProvider synapseContext={mockSynapseContext}>
<GoalsV2
entityId="syn22315959"
dataLink="https://eliteportal.synapse.org/Explore/Data"
/>
</SynapseContextProvider>
</QueryClientProvider>,
)

await waitFor(() => {
expect(screen.getByText("What's in the Portal?")).toBeInTheDocument()
})
})

test('images do not display on fetch failure', async () => {
server.use(
rest.get(
'https://repo-prod.prod.sagebase.org/repo/v1/entity/:entityId/table/query/async/get/:token',
(req, res, ctx) => {
return res(
ctx.status(500),
ctx.json({ message: 'Internal Server Error' }),
)
},
),
)

render(
<QueryClientProvider client={queryClient}>
<SynapseContextProvider synapseContext={mockSynapseContext}>
<GoalsV2
entityId="synxyz"
dataLink="https://eliteportal.synapse.org/Explore/Data"
/>
</SynapseContextProvider>
</QueryClientProvider>,
)

await waitFor(() => {
expect(screen.queryByRole('img')).not.toBeInTheDocument()
})
})

test('displays assets when fetch is successful', async () => {
render(
<QueryClientProvider client={queryClient}>
<SynapseContextProvider synapseContext={mockSynapseContext}>
<GoalsV2
entityId="syn22315959"
dataLink="https://eliteportal.synapse.org/Explore/Data"
/>
</SynapseContextProvider>
</QueryClientProvider>,
)

await waitFor(() => {
expect(screen.getByText("What's in the Portal?")).toBeInTheDocument()
})
})
})
Loading
Loading