Skip to content

Commit

Permalink
feat: bookmark implementation
Browse files Browse the repository at this point in the history
Signed-off-by: Raul Kele <raulkeleblk@gmail.com>
  • Loading branch information
raulkele committed May 12, 2023
1 parent 769ffdc commit 05d5f74
Show file tree
Hide file tree
Showing 24 changed files with 446 additions and 129 deletions.
113 changes: 106 additions & 7 deletions src/__tests__/Explore/Explore.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const mockImageList = {
Name: 'alpine',
Size: '2806985',
LastUpdated: '2022-08-09T17:19:53.274069586Z',
IsBookmarked: false,
NewestImage: {
Tag: 'latest',
Description: 'w',
Expand All @@ -44,12 +45,19 @@ const mockImageList = {
MaxSeverity: 'LOW',
Count: 7
}
}
},
Platforms: [
{
Os: 'linux',
Arch: 'amd64'
}
]
},
{
Name: 'mongo',
Size: '231383863',
LastUpdated: '2022-08-02T01:30:49.193203152Z',
IsBookmarked: false,
NewestImage: {
Tag: 'latest',
Description: '',
Expand All @@ -61,12 +69,19 @@ const mockImageList = {
MaxSeverity: 'HIGH',
Count: 2
}
}
},
Platforms: [
{
Os: 'linux',
Arch: 'amd64'
}
]
},
{
Name: 'node',
Size: '369311301',
LastUpdated: '2022-08-23T00:20:40.144281895Z',
IsBookmarked: false,
NewestImage: {
Tag: 'latest',
Description: '',
Expand All @@ -78,12 +93,19 @@ const mockImageList = {
MaxSeverity: 'CRITICAL',
Count: 10
}
}
},
Platforms: [
{
Os: 'linux',
Arch: 'amd64'
}
]
},
{
Name: 'centos',
Size: '369311301',
LastUpdated: '2022-08-23T00:20:40.144281895Z',
IsBookmarked: false,
NewestImage: {
Tag: 'latest',
Description: '',
Expand All @@ -95,12 +117,19 @@ const mockImageList = {
MaxSeverity: 'NONE',
Count: 10
}
}
},
Platforms: [
{
Os: 'linux',
Arch: 'amd64'
}
]
},
{
Name: 'debian',
Size: '369311301',
LastUpdated: '2022-08-23T00:20:40.144281895Z',
IsBookmarked: false,
NewestImage: {
Tag: 'latest',
Description: '',
Expand All @@ -112,12 +141,23 @@ const mockImageList = {
MaxSeverity: 'MEDIUM',
Count: 10
}
}
},
Platforms: [
{
Os: 'linux',
Arch: 'amd64'
},
{
Os: 'windows',
Arch: 'amd64'
}
]
},
{
Name: 'mysql',
Size: '369311301',
LastUpdated: '2022-08-23T00:20:40.144281895Z',
IsBookmarked: false,
NewestImage: {
Tag: 'latest',
Description: '',
Expand All @@ -129,12 +169,19 @@ const mockImageList = {
MaxSeverity: 'UNKNOWN',
Count: 10
}
}
},
Platforms: [
{
Os: 'linux',
Arch: 'amd64'
}
]
},
{
Name: 'base',
Size: '369311301',
LastUpdated: '2022-08-23T00:20:40.144281895Z',
IsBookmarked: false,
NewestImage: {
Tag: 'latest',
Description: '',
Expand All @@ -146,12 +193,40 @@ const mockImageList = {
MaxSeverity: '',
Count: 10
}
}
},
Platforms: [
{
Os: 'linux',
Arch: 'amd64'
}
]
}
]
}
};

const filteredMockImageListWindows = () => {
const filteredRepos = mockImageList.GlobalSearch.Repos.filter((r) =>
r.Platforms.map((pf) => pf.Os).includes('windows')
);
return {
GlobalSearch: {
Page: { TotalCount: 1, ItemCount: 1 },
Repos: filteredRepos
}
};
};

const filteredMockImageListSigned = () => {
const filteredRepos = mockImageList.GlobalSearch.Repos.filter((r) => r.NewestImage.IsSigned);
return {
GlobalSearch: {
Page: { TotalCount: 6, ItemCount: 6 },
Repos: filteredRepos
}
};
};

beforeEach(() => {
// IntersectionObserver isn't available in test environment
const mockIntersectionObserver = jest.fn();
Expand Down Expand Up @@ -235,4 +310,28 @@ describe('Explore component', () => {
const filterCheckboxes = await screen.findAllByRole('checkbox');
expect(filterCheckboxes[0]).toBeChecked();
});

it('should filter the images based on filter cards', async () => {
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImageList } });
render(<StateExploreWrapper />);
expect(await screen.findAllByTestId('repo-card')).toHaveLength(mockImageList.GlobalSearch.Repos.length);
const windowsCheckbox = (await screen.findAllByRole('checkbox'))[0];
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: filteredMockImageListWindows() } });
await userEvent.click(windowsCheckbox);
expect(windowsCheckbox).toBeChecked();
expect(await screen.findAllByTestId('repo-card')).toHaveLength(1);
const signedCheckboxLabel = await screen.findByText(/signed images/i);
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: filteredMockImageListSigned() } });
await userEvent.click(signedCheckboxLabel);
expect(await screen.findAllByTestId('repo-card')).toHaveLength(6);
});

it('should bookmark a repo if bookmark button is clicked', async () => {
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImageList } });
render(<StateExploreWrapper />);
const bookmarkButton = (await screen.findAllByTestId('bookmark-button'))[0];
jest.spyOn(api, 'put').mockResolvedValueOnce({ status: 200, data: {} });
await userEvent.click(bookmarkButton);
expect(await screen.findAllByTestId('bookmarked')).toHaveLength(1);
});
});
71 changes: 60 additions & 11 deletions src/__tests__/HomePage/Home.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,48 @@ const mockImageListRecent = {
}
};

const mockImageListBookmarks = {
GlobalSearch: {
Page: { TotalCount: 3, ItemCount: 2 },
Repos: [
{
Name: 'alpine',
Size: '2806985',
LastUpdated: '2022-08-09T17:19:53.274069586Z',
NewestImage: {
Tag: 'latest',
Description: 'w',
IsSigned: false,
Licenses: '',
Vendor: '',
Labels: '',
Vulnerabilities: {
MaxSeverity: 'LOW',
Count: 7
}
}
},
{
Name: 'mongo',
Size: '231383863',
LastUpdated: '2022-08-02T01:30:49.193203152Z',
NewestImage: {
Tag: 'latest',
Description: '',
IsSigned: true,
Licenses: '',
Vendor: '',
Labels: '',
Vulnerabilities: {
MaxSeverity: 'HIGH',
Count: 2
}
}
}
]
}
};

beforeEach(() => {
window.scrollTo = jest.fn();
});
Expand All @@ -134,43 +176,45 @@ afterEach(() => {
describe('Home component', () => {
it('fetches image data and renders popular, bookmarks and recently updated', async () => {
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImageList } });
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImageListRecent } });
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageListRecent } });
render(<HomeWrapper />);
await waitFor(() => expect(screen.getAllByText(/alpine/i)).toHaveLength(2));
await waitFor(() => expect(screen.getAllByText(/mongo/i)).toHaveLength(2));
await waitFor(() => expect(screen.getAllByText(/alpine/i)).toHaveLength(3));
await waitFor(() => expect(screen.getAllByText(/mongo/i)).toHaveLength(3));
await waitFor(() => expect(screen.getAllByText(/node/i)).toHaveLength(1));
});

it('renders signature icons', async () => {
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImageList } });
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImageListRecent } });
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageListRecent } });
render(<HomeWrapper />);
expect(await screen.findAllByTestId('unverified-icon')).toHaveLength(2);
expect(await screen.findAllByTestId('verified-icon')).toHaveLength(3);
expect(await screen.findAllByTestId('unverified-icon')).toHaveLength(3);
expect(await screen.findAllByTestId('verified-icon')).toHaveLength(4);
});

it('renders vulnerability icons', async () => {
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImageList } });
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImageListRecent } });
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockImageListRecent } });
render(<HomeWrapper />);
expect(await screen.findAllByTestId('low-vulnerability-icon')).toHaveLength(2);
expect(await screen.findAllByTestId('high-vulnerability-icon')).toHaveLength(2);
expect(await screen.findAllByTestId('low-vulnerability-icon')).toHaveLength(3);
expect(await screen.findAllByTestId('high-vulnerability-icon')).toHaveLength(3);
expect(await screen.findAllByTestId('critical-vulnerability-icon')).toHaveLength(1);
});

it("should log an error when data can't be fetched", async () => {
jest.spyOn(api, 'get').mockRejectedValue({ status: 500, data: {} });
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<HomeWrapper />);
await waitFor(() => expect(error).toBeCalledTimes(2));
await waitFor(() => expect(error).toBeCalledTimes(3));
});

it('should redirect to explore page when clicking view all popular', async () => {
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImageList } });
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImageListRecent } });
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImageListBookmarks } });
render(<HomeWrapper />);
const viewAllButtons = await screen.findAllByText(/view all/i);
expect(viewAllButtons).toHaveLength(2);
expect(viewAllButtons).toHaveLength(3);
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: [] } });
fireEvent.click(viewAllButtons[0]);
expect(mockedUsedNavigate).toHaveBeenCalledWith({
pathname: `/explore`,
Expand All @@ -181,5 +225,10 @@ describe('Home component', () => {
pathname: `/explore`,
search: createSearchParams({ sortby: sortByCriteria.updateTime.value }).toString()
});
fireEvent.click(viewAllButtons[2]);
expect(mockedUsedNavigate).toHaveBeenCalledWith({
pathname: `/explore`,
search: createSearchParams({ filter: 'IsBookmarked' }).toString()
});
});
});
11 changes: 11 additions & 0 deletions src/__tests__/RepoPage/Repo.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React from 'react';
import { api } from 'api';
import { createSearchParams } from 'react-router-dom';
import MockThemeProvier from '__mocks__/MockThemeProvider';
import userEvent from '@testing-library/user-event';

const RepoDetailsThemeWrapper = () => {
return (
Expand Down Expand Up @@ -45,6 +46,7 @@ const mockRepoDetailsData = {
LastUpdated: '2023-01-30T15:05:35.420124619Z',
Size: '451554070',
Vendors: ['[The Node.js Docker Team](https://github.com/nodejs/docker-node)\n'],
IsBookmarked: false,
NewestImage: {
RepoName: 'mongo',
IsSigned: true,
Expand Down Expand Up @@ -298,4 +300,13 @@ describe('Repo details component', () => {
search: createSearchParams({ filter: 'linux' }).toString()
});
});

it('should bookmark a repo if bookmark button is clicked', async () => {
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockRepoDetailsData } });
render(<RepoDetailsThemeWrapper />);
const bookmarkButton = await screen.findByTestId('bookmark-button');
jest.spyOn(api, 'put').mockResolvedValue({ status: 200, data: {} });
await userEvent.click(bookmarkButton);
expect(await screen.findByTestId('bookmarked')).toBeInTheDocument();
});
});
Loading

0 comments on commit 05d5f74

Please sign in to comment.