Skip to content

Commit

Permalink
Adds the components needed for dataset search pages (#5)
Browse files Browse the repository at this point in the history
* Add basic search page template with search list item

* Add facets and pagination to dataset search page

* Add DatasetSearchListItem tests

* Add tests to DatasetSearchFacets

* Add some basic tests to DatasetSearch and a mocks folder for axios mocking
  • Loading branch information
dgading authored Feb 3, 2021
1 parent 24a86ab commit 4be6095
Show file tree
Hide file tree
Showing 15 changed files with 461 additions and 4 deletions.
1 change: 1 addition & 0 deletions __mocks__/styleMock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = {};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"prepublish": "npm run lib",
"test": "jest --verbose",
"test:watch": "npm run test -- --watch",
"test:coverage": "npm run test -- --coverage",
"docz:dev": "docz dev",
"docz:build": "docz build",
"docz:serve": "docz build && docz serve",
Expand Down
48 changes: 48 additions & 0 deletions src/components/DatasetSearchFacets/dataset_search_facets.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import DatasetSearchFacets from './index';
import { isSelected } from './index';

const testFacets = [{type: 'theme', name: 'facet-1', total: '2'}, {type: 'theme', name: 'facet-2', total: '3'}]

describe('isSelected Function', () => {
test('returns -1 if not selected', () => {
expect(isSelected('dkan', [''])).toEqual(-1);
});
test('returns correct index if item in array', () => {
expect(isSelected('dkan', ['dkan'])).toEqual(0);
expect(isSelected('react', ['dkan', 'react'])).toEqual(1);
});
})


describe('<DatasetSearchFacets />', () => {
test('Renders correctly', () => {
render(<DatasetSearchFacets title="Facets" facets={testFacets} onclickFunction={() => ({})} />);
expect(screen.getByRole('button', { name: "Facets" })).toBeInTheDocument();
expect(screen.getByLabelText('facet-1 (2)')).toBeInTheDocument();
expect(screen.getByLabelText('facet-2 (3)')).toBeInTheDocument();
expect(screen.getAllByRole('checkbox')).toHaveLength(2);
});
test('Opens and closes, hiding facets', () => {
render(<DatasetSearchFacets title="Facets" facets={testFacets} onclickFunction={() => ({})} />);
expect(screen.getAllByRole('checkbox')).toHaveLength(2);
fireEvent.click(screen.getByRole('button', { name: "Facets" }));
expect(screen.queryByLabelText('facet-1 (2)')).toBeNull();
expect(screen.queryAllByRole('checkbox')).toHaveLength(0);
fireEvent.click(screen.getByRole('button', { name: "Facets" }));
expect(screen.getAllByRole('checkbox')).toHaveLength(2);
});
test('Checkbox fires onclickFunction', () => {
const handleClick = jest.fn()
render(<DatasetSearchFacets title="Facets" facets={testFacets} onclickFunction={handleClick} />);
fireEvent.click(screen.getByRole('checkbox', { name: 'facet-1 (2)'}));
expect(handleClick).toHaveBeenCalledTimes(1)
});
test('Checkbox is checked if in selectedFacets array', () => {
const handleClick = jest.fn()
render(<DatasetSearchFacets title="Facets" facets={testFacets} onclickFunction={handleClick} selectedFacets={['facet-1']} />);
expect(screen.getByRole('checkbox', { name: 'facet-1 (2)', checked: true})).toBeInTheDocument()
});
});
59 changes: 59 additions & 0 deletions src/components/DatasetSearchFacets/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Choice, Button } from '@cmsgov/design-system'

export function isSelected(currentFacet, selectedFacets) {
let isSelected = -1;
if(selectedFacets) {
isSelected = selectedFacets.findIndex((s) => s === currentFacet);
}
return isSelected;
}

const DatasetSearchFacets = ({ title, facets, onclickFunction, selectedFacets }) => {
const [isOpen, setIsOpen] = useState(true);
const filteredFacets = facets.filter((f) => (f.total > 0));


return (
<div className="ds-u-margin-bottom--4">
<Button
variation="transparent"
className={`dc-facet-block--toggle ds-h4 ds-u-margin-top--0 ds-u-padding-left--0 ${isOpen ? 'open' : 'closed'}`}
onClick={() => setIsOpen(!isOpen)}
>
{title}
</Button>
{isOpen
&&(
<ul className="dc-dataset-search--facets ds-u-padding--0 ds-u-margin--0">
{facets.map((f) => {
return (<li key={f.name}>
<Choice
checked={isSelected(f.name, selectedFacets) > -1 ? true : false}
name={`facet_theme_${f.name}`}
type="checkbox"
label={`${f.name} (${f.total})`}
value={f.name}
onClick={(e) => onclickFunction({key: f.type, value: e.target.value})}
/>
</li>)
})}
</ul>
)
}
</div>
);
}

DatasetSearchFacets.propTypes = {
title: PropTypes.string.isRequired,
facets: PropTypes.arrayOf(PropTypes.shape({
type: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
total: PropTypes.string.isRequired,
})).isRequired,
onclickFunction: PropTypes.func.isRequired,
}

export default DatasetSearchFacets;
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import { render, screen, within } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import DatasetSearchListItem from './index';

const singleItem = {
title: "Dataset Title",
modified: "2020-10-22",
description: "This is my description.",
theme: ["dkan"],
keyword: ["my keyword"]
}

describe('<DatasetSearchListItem />', () => {
test('Renders correctly', () => {
render(<DatasetSearchListItem item={singleItem} />);
const listItemOptions = singleItem.theme.concat(singleItem.keyword)
const listItems = screen.getAllByRole('listitem')
listItems.forEach((item, idx) => {
const { getByText } = within(item);
expect(getByText(listItemOptions[idx])).toBeInTheDocument();
})


expect(screen.getByRole('heading', { name: "Dataset Title" })).toBeInTheDocument();
expect(screen.getByRole('link', { name: "Dataset Title" })).toBeInTheDocument();
expect(screen.getByText('Updated October 22, 2020')).toBeInTheDocument();
expect(screen.getByText('This is my description.')).toBeInTheDocument();
});
});
50 changes: 50 additions & 0 deletions src/components/DatasetSearchListItem/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react';
import { Link } from '@reach/router';
import { Button, Badge } from '@cmsgov/design-system';

const DatasetSearchListItem = ({item, updateFacets}) => {
const { title, modified, description, theme, keyword, identifier } = item;
const updatedDate = new Date(modified.replace(/-/g, '\/'))
const dateOptions = {month: 'long', year: 'numeric', day: 'numeric'}
return(
<div className="dc-dataset-searchlist-item ds-u-border-top--1 ds-u-margin-bottom--5">
<div className="ds-u-display--flex ds-u-flex-direction--row ds-u-justify-content--between ds-u-padding-top--5">
{theme &&
<ul className="ds-u-padding--0 ds-u-display--flex ds-u-flex-direction--row">
{theme.map((t) => (
<li key={t} className="ds-u-margin-right--1">
<Badge variation="info">{t}</Badge>
</li>
))}
</ul>
}
<span className="ds-u-color--gray">
Updated {`${updatedDate.toLocaleDateString(undefined, dateOptions)}`}
</span>
</div>
<h3>
<Link
className="ds-u-color--base"
to={`/dataset/${identifier}`}
>
{title}
</Link>
</h3>
{/* 215 average character limit */}
<p className="ds-u-margin-top--0">{description}</p>
<div>
{keyword &&
<ul className="ds-u-padding--0">
{keyword.map((k) => (
<li key={k}>
<Badge className="ds-u-radius ds-u-fill--primary-alt-lightest ds-u-color--base" variation="info">{k}</Badge>
</li>
))}
</ul>
}
</div>
</div>
);
}

export default DatasetSearchListItem;
15 changes: 12 additions & 3 deletions src/components/Pagination/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Button } from '@cmsgov/design-system';
import { usePagination, buildPageArray } from '@civicactions/data-catalog-services';

const Pagination = ({
currentPage, totalItems, itemsPerPage, gotoPage,
currentPage, totalItems, itemsPerPage, gotoPage, calcByOffset
}) => {
const {
pageIndex,
Expand All @@ -13,11 +13,20 @@ const Pagination = ({
canGoToNext,
goToNext,
goToPrevious,
} = usePagination(0, totalItems, itemsPerPage);
setTotalItems,
} = usePagination(currentPage, totalItems, itemsPerPage);
const pageButtons = buildPageArray(pageIndex, 2, pages);

useEffect(() => {
setTotalItems(totalItems)
}, [totalItems])

useEffect(()=> {
gotoPage((Number(pageIndex)) * itemsPerPage)
if (calcByOffset) {
gotoPage((Number(pageIndex)) * itemsPerPage)
} else {
gotoPage((Number(pageIndex)))
}
}, [pageIndex])
return (
<div className="dc-pagination ds-u-display--flex ds-u-flex-direction--row ds-u-justify-content--between ds-u-align-items--center">
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export { default as Pagination } from './components/Pagination';
// Templates
export { default as Footer } from './templates/Footer';
export { default as Dataset } from './templates/Dataset';
export { default as DatasetSearch } from './templates/DatasetSearch';
export { default as DrupalPage } from './templates/DrupalPage';
3 changes: 3 additions & 0 deletions src/styles/scss/components/dataset-search-facets.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.dc-dataset-search--facets {
list-style: none;
}
14 changes: 14 additions & 0 deletions src/styles/scss/components/dataset-search-list-item.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@import "~@cmsgov/design-system/dist/scss/settings/variables/color";

.dc-dataset-searchlist-item {

ul {
list-style: none;
}

.dc-dataset-searchlist-item--keyword {
background: $color-primary-alt-lightest;
border: none;
color: $color-base;
}
}
4 changes: 3 additions & 1 deletion src/styles/scss/components/index.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
@import "./datatable.scss";
@import "./dataset-tags.scss";
@import "./dataset-downloads.scss";
@import "./pagination.scss";
@import "./pagination.scss";
@import "./dataset-search-list-item.scss";
@import "./dataset-search-facets.scss";
28 changes: 28 additions & 0 deletions src/styles/scss/templates/dataset-search.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@import "~@cmsgov/design-system/dist/scss/settings/variables/color";

.dc-dataset-search-list {
list-style: none;
}

.dc-fulltext--input-container {
width: 100%;
input {
max-width: inherit;
}
}

.dc-search-header {
display: relative;
&::after {
content: "";
display: block;
width: 48px;
height: 4px;
margin: 8px 0;
background-color: $color-primary-alt-light;
}
}

.dataset-results-count {
font-weight: bold;
}
1 change: 1 addition & 0 deletions src/styles/scss/templates/index.scss
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
@import "./dataset-search.scss";
@import "./footer.scss";
62 changes: 62 additions & 0 deletions src/templates/DatasetSearch/datasetsearch.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React from 'react';
import axios from 'axios';
import {act} from 'react-dom/test-utils';
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import DatasetSearch, { selectedFacetsMessage } from './index';

jest.mock('axios');
jest.useFakeTimers();
const rootUrl = 'http://dkan.com/api/1';
const data_results = {
data: {
total: '1',
results: {
'dkan_dataset/5d69-frba': {title: 'dkan'}
},
facets: [
{
type: 'theme',
name: 'general',
total: '2'
},
],
}
};

describe('selectedFacetsMessage', () => {
test('turns selectedFacets and titles into a string', () => {
const selectedFacets = {theme: ['dkan'], keyword: ['react']};
expect(
selectedFacetsMessage(selectedFacets, {theme: 'Categories', keyword: 'Tags'})
).toEqual('Categories: dkan & Tags: react')
})
})

describe('<DatasetSearchFacets />', () => {
test('Renders correctly', async () => {
await axios.get.mockImplementation(() => Promise.resolve(data_results));
const { debug } = render(<DatasetSearch rootUrl={rootUrl} />);
await act(async () => {



// debug()
jest.useFakeTimers();

});
// debug()
// expect(axios.get).toHaveBeenCalledWith(
// `${rootUrl}/search/?`,
// );
// await act(async () => { });

expect(screen.getByRole('heading', {name: 'Datasets'}))
expect(screen.getByRole('textbox', {name: 'Search term'}))
expect(screen.getByRole('button', {name: 'Clear all filters'}))
expect(screen.getByRole('combobox', {name: 'Sort by'}))
expect(screen.getByRole('button', {name: 'Search'}))
expect(screen.getByText(/0-0 out of 0/i));
expect(screen.getByText('[0 entries total on page]'));
})
})
Loading

0 comments on commit 4be6095

Please sign in to comment.