repo page tests

added coverage, fixed some small linting issues

Signed-off-by: Raul Kele <raulkeleblk@gmail.com>
This commit is contained in:
Raul Kele 2022-08-16 09:42:06 +03:00 committed by Andrei Aaron
parent 1b91668036
commit b4b5259469
11 changed files with 252 additions and 13 deletions

View File

@ -0,0 +1,88 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import RepoDetails from 'components/RepoDetails';
import React from 'react';
import { api } from 'api';
// uselocation mock
const mockUseLocationValue = {
pathname: "'localhost:3000/image/test'",
search: '',
hash: '',
state:{ lastDate: '' }
}
jest.mock("react-router-dom", () => ({
// @ts-ignore
...jest.requireActual("react-router-dom"),
useParams: () =>{return {name:'test'} },
useLocation: () => {
return mockUseLocationValue;
}
}));
// mock clipboard copy fn
const mockCopyToClipboard = jest.fn();
Object.assign(navigator, {
clipboard: {
writeText: mockCopyToClipboard,
},
});
const mockRepoDetailsData = {
"ExpandedRepoInfo": {
"Manifests": [
{
"Digest": "2aa7ff5ca352d4d25fc6548f9930a436aacd64d56b1bd1f9ff4423711b9c8718",
"Tag": "latest",
"Layers": [
{
"Size": "2798889",
"Digest": "2408cc74d12b6cd092bb8b516ba7d5e290f485d3eb9672efc00f0583730179e8"
}
]
}
]
}
}
afterEach(() => {
// restore the spy created with spyOn
jest.restoreAllMocks();
});
describe('Repo details component', () => {
it('fetches repo detailed data and renders',async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsData } });
render(<RepoDetails/>);
expect(await screen.findByText('test')).toBeInTheDocument();
});
it('should log error if data can\'t be fetched', async () => {
jest.spyOn(api, 'get').mockRejectedValue({ status: 500, data: { } })
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<RepoDetails/>);
await waitFor(() => expect(error).toBeCalledTimes(1));
});
it('should switch between tabs', async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsData } });
render(<RepoDetails/>);
expect(screen.getByTestId('overview-container')).toBeInTheDocument();
fireEvent.click(await screen.findByText(/tags/i));
expect(screen.getByTestId('tags-container')).toBeInTheDocument();
expect(screen.queryByTestId('overview-container')).not.toBeInTheDocument();
});
it('should copy the pull string to clipboard',async () => {
// @ts-ignore
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsData } });
render(<RepoDetails/>);
fireEvent.click(screen.getByTestId('pullcopy-btn'));
await waitFor(() => expect(mockCopyToClipboard).toHaveBeenCalledWith('Pull test'));
});
});

View File

@ -0,0 +1,30 @@
import { render, screen } from '@testing-library/react';
import RepoPage from 'pages/RepoPage';
import React from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
jest.mock("react-router-dom", () => ({
// @ts-ignore
...jest.requireActual("react-router-dom"),
useLocation: () => ({
pathname:'localhost:3000/image/test',
state: { lastDate: '' }
})
}));
afterEach(() => {
// restore the spy created with spyOn
jest.restoreAllMocks();
});
it('renders the repository page component', () => {
render(
<BrowserRouter>
<Routes>
<Route path="*" element={<RepoPage updateKeywords={() => {}}/>} />
</Routes>
</BrowserRouter>
);
expect(screen.getByTestId('repo-container')).toBeInTheDocument();
});

View File

@ -0,0 +1,29 @@
import { fireEvent, render, screen } from '@testing-library/react';
import Tags from 'components/Tags';
import React from 'react';
const mockedTagsData = {
name: 'test',
tags: [
{
"Digest": "2aa7ff5ca352d4d25fc6548f9930a436aacd64d56b1bd1f9ff4423711b9c8718",
"Tag": "latest",
"Layers": [
{
"Size": "2798889",
"Digest": "2408cc74d12b6cd092bb8b516ba7d5e290f485d3eb9672efc00f0583730179e8"
}
]
}
]
};
describe('Tags component', () => {
it('should open and close details dropdown for tags', () => {
render(<Tags data={mockedTagsData}/>);
const openBtn = screen.getByText(/see layers/i);
fireEvent.click(openBtn);
expect(screen.queryByText(/see layers/i)).not.toBeInTheDocument();
expect(screen.getByText(/hide layers/i)).toBeInTheDocument();
});
})

View File

@ -0,0 +1,41 @@
import { render, screen } from '@testing-library/react';
import React from 'react';
import userEvent from '@testing-library/user-event';
import PreviewCard from 'components/PreviewCard';
// usenavigate mock
const mockedUsedNavigate = jest.fn();
jest.mock("react-router-dom", () => ({
// @ts-ignore
...jest.requireActual("react-router-dom"),
useNavigate: () => mockedUsedNavigate
}));
// image mock
const mockImage = {
name: "alpine",
latestVersion: "latest",
lastUpdated: "2022-05-23T19:19:30.413290187Z",
description: "",
licenses: "",
vendor: "",
size: "585",
tags: ""
};
afterEach(() => {
// restore the spy created with spyOn
jest.restoreAllMocks();
});
describe('Preview card component', () => {
it('navigates to repo page when clicked',async () => {
render(<PreviewCard name={mockImage.name} lastUpdated={mockImage.lastUpdated}/>);
const cardTitle = await screen.findByText('alpine');
expect(cardTitle).toBeInTheDocument();
userEvent.click(cardTitle);
expect(mockedUsedNavigate).toBeCalledWith(`/image/${mockImage.name}`, expect.anything());
});
});

View File

@ -0,0 +1,53 @@
import { render, screen } from '@testing-library/react';
import React from 'react';
import PreviewCard from 'components/PreviewCard';
import userEvent from '@testing-library/user-event';
// usenavigate mock
const mockedUsedNavigate = jest.fn();
jest.mock("react-router-dom", () => ({
// @ts-ignore
...jest.requireActual("react-router-dom"),
useNavigate: () => mockedUsedNavigate
}));
// image mock
const mockImage = {
name: "alpine",
latestVersion: "latest",
lastUpdated: "2022-05-23T19:19:30.413290187Z",
description: "",
licenses: "",
vendor: "",
size: "585",
tags: ""
};
afterEach(() => {
// restore the spy created with spyOn
jest.restoreAllMocks();
});
describe('Repo card component', () => {
it('navigates to repo page when clicked',async () => {
render(<PreviewCard
name={mockImage.name}
version={mockImage.latestVersion}
description={mockImage.description}
tags={mockImage.tags}
vendor={mockImage.vendor}
size={mockImage.size}
licenses={mockImage.licenses}
key={1}
data={mockImage}
lastUpdated={mockImage.lastUpdated}
shown={true}
/>);
const cardTitle = await screen.findByText('alpine');
expect(cardTitle).toBeInTheDocument();
userEvent.click(cardTitle);
expect(mockedUsedNavigate).toBeCalledWith(`/image/${mockImage.name}`, expect.anything());
});
});

View File

@ -11,7 +11,6 @@ import makeStyles from '@mui/styles/makeStyles';
import React from "react";
const useStyles = makeStyles((theme) => {
console.log("theme", theme)
return {
exploreHeader: {
backgroundColor: "#FFFFFF",

View File

@ -1,4 +1,4 @@
import { Card, CardActionArea, CardContent, CardMedia, Chip, Grid, Stack, Typography } from '@mui/material';
import { Card, CardActionArea, CardContent, CardMedia, Grid, Stack, Typography } from '@mui/material';
import { makeStyles } from '@mui/styles';
import { DateTime } from 'luxon';
import React from 'react';
@ -61,7 +61,7 @@ const useStyles = makeStyles(() => ({
}
}));
function RepoCard(props) {
function PreviewCard(props) {
const classes = useStyles();
const navigate = useNavigate();
const { name, lastUpdated } = props;
@ -110,4 +110,4 @@ function RepoCard(props) {
);
};
export default RepoCard;
export default PreviewCard;

View File

@ -5,7 +5,7 @@ import { useNavigate } from "react-router-dom";
// utility
import { DateTime } from 'luxon';
// components
import { Card, CardActionArea, CardMedia, CardContent, Typography, Stack, Chip, Box, Grid } from '@mui/material';
import { Card, CardActionArea, CardMedia, CardContent, Typography, Stack, Chip, Grid } from '@mui/material';
import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined';
import BookmarkIcon from '@mui/icons-material/Bookmark';
import makeStyles from '@mui/styles/makeStyles';

View File

@ -108,9 +108,9 @@ function RepoDetails (props) {
// get url param from <Route here (i.e. image name)
const {name} = useParams();
const {state} = useLocation();
const {state} = useLocation() || {};
// @ts-ignore
const {lastDate} = state;
const {lastDate} = state || {};
const classes = useStyles();
const {description, overviewTitle, dependencies, dependents} = props;
@ -163,7 +163,7 @@ function RepoDetails (props) {
const renderOverview = () => {
return (
<Card className={classes.card}>
<Card className={classes.card} data-testid='overview-container'>
<CardContent>
<Typography variant="h4" align="left">{overviewTitle || 'Quickstart'}</Typography>
<Typography variant="body1" sx={{color:"rgba(0, 0, 0, 0.6)", fontSize:"1rem",lineHeight:"150%", marginTop:"5%"}}>{description || mockData.loremIpsum}</Typography>
@ -232,7 +232,7 @@ function RepoDetails (props) {
value={`Pull ${name}`}
endAdornment={
<InputAdornment position="end">
<IconButton aria-label='copy' edge="end" onClick={() => navigator.clipboard.writeText(`Pull ${name}`)}>
<IconButton aria-label='copy' edge="end" onClick={() => navigator.clipboard.writeText(`Pull ${name}`)} data-testid='pullcopy-btn'>
<ContentCopyIcon/>
</IconButton>
</InputAdornment>

View File

@ -89,7 +89,6 @@ TagCard.propTypes = {
row: PropTypes.shape({
Layers: PropTypes.arrayOf(
PropTypes.shape({
amount: PropTypes.number.isRequired,
Digest: PropTypes.string.isRequired,
Size: PropTypes.string.isRequired,
}),
@ -109,13 +108,13 @@ const renderTags = (tags) => {
}
export default function CollapsibleTable(props) {
export default function Tags(props) {
const classes = useStyles();
const {data} = props;
const {tags} = data;
return (
<Card className={classes.card}>
<Card className={classes.card} data-testid='tags-container'>
<CardContent className={classes.content}>
<Typography variant="h4" gutterBottom component="div" align="left" style={{color: "rgba(0, 0, 0, 0.87)"}}>Tags history</Typography>
<Divider variant="fullWidth" sx={{margin:"5% 0% 5% 0%", background:"rgba(0, 0, 0, 0.38)", height:"0.0625rem", width:"100%"}}/>

View File

@ -33,7 +33,7 @@ function RepoPage(props) {
const classes = useStyles();
return (
<Stack direction="column" className={classes.pageWrapper}>
<Stack direction="column" className={classes.pageWrapper} data-testid='repo-container'>
<Header updateKeywords={props.updateKeywords}></Header>
<Container className={classes.container} >
<ExploreHeader/>