From ce24a58c99831ef0deb1c7ea0d7c1ff10a77dd88 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 27 Oct 2023 09:34:29 -0500 Subject: [PATCH] feat: Add courseware search results filter container (#1225) --- .../CoursewareResultsFilter.jsx | 50 +++++++++++ .../CoursewareResultsFilter.test.jsx | 85 +++++++++++++++++++ .../courseware-search/CoursewareSearch.jsx | 6 +- ...rsewareSearchResult.PropTypeDefinition.jsx | 12 +++ 4 files changed, 149 insertions(+), 4 deletions(-) create mode 100644 src/course-home/courseware-search/CoursewareResultsFilter.jsx create mode 100644 src/course-home/courseware-search/CoursewareResultsFilter.test.jsx create mode 100644 src/course-home/courseware-search/CoursewareSearchResult.PropTypeDefinition.jsx diff --git a/src/course-home/courseware-search/CoursewareResultsFilter.jsx b/src/course-home/courseware-search/CoursewareResultsFilter.jsx new file mode 100644 index 0000000000..9232041479 --- /dev/null +++ b/src/course-home/courseware-search/CoursewareResultsFilter.jsx @@ -0,0 +1,50 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { Tabs, Tab } from '@edx/paragon'; + +import CoursewareSearchResultPropType from './CoursewareSearchResult.PropTypeDefinition'; +import CoursewareSearchResults from './CoursewareSearchResults'; + +export const filteredResultsBySelection = ({ filterKey = 'all', results = [] }) => { + if (['document', 'video', 'text'].includes(filterKey)) { + return results.filter(result => result?.type?.toLowerCase() === filterKey); + } + + return results || []; +}; + +const tabConfiguration = [ + { eventKey: 'all', title: 'All content' }, + { eventKey: 'document', title: 'Course outline' }, + { eventKey: 'text', title: 'Text' }, + { eventKey: 'video', title: 'Video' }, +]; + +export const CoursewareSearchResultsFilter = ({ intl, results }) => { + if (!results || !results.length) { return null; } + + return ( + + {tabConfiguration.map((tab) => ( + + + + ))} + + ); +}; + +CoursewareSearchResultsFilter.propTypes = { + intl: intlShape.isRequired, + results: PropTypes.arrayOf(CoursewareSearchResultPropType), +}; + +CoursewareSearchResultsFilter.defaultProps = { + results: [], +}; + +export default injectIntl(CoursewareSearchResultsFilter); diff --git a/src/course-home/courseware-search/CoursewareResultsFilter.test.jsx b/src/course-home/courseware-search/CoursewareResultsFilter.test.jsx new file mode 100644 index 0000000000..8052ab6cba --- /dev/null +++ b/src/course-home/courseware-search/CoursewareResultsFilter.test.jsx @@ -0,0 +1,85 @@ +import React from 'react'; +import { + initializeMockApp, + render, + screen, + waitFor, +} from '../../setupTest'; +import { CoursewareSearchResultsFilter, filteredResultsBySelection } from './CoursewareResultsFilter'; + +const mockResults = [ + { type: 'video', title: 'video_title' }, + { type: 'video', title: 'video_title2' }, + { type: 'document', title: 'document_title' }, + { type: 'text', title: 'text_title1' }, + { type: 'text', title: 'text_title2' }, + { type: 'text', title: 'text_title3' }, +]; + +describe('CoursewareSearchResultsFilter', () => { + beforeAll(initializeMockApp); + + describe('filteredResultsBySelection', () => { + it('returns a no values array when no results are provided', () => { + const results = filteredResultsBySelection({ results: [] }); + + expect(results.length).toEqual(0); + }); + + it('returns all values when no key value is provided', () => { + const results = filteredResultsBySelection({ results: mockResults }); + + expect(results.length).toEqual(mockResults.length); + }); + + it('returns all values when the key value "all" is provided', () => { + const results = filteredResultsBySelection({ filterKey: 'all', results: mockResults }); + + expect(results.length).toEqual(mockResults.length); + }); + + it('returns only "video"-typed elements when the key value "video" is given', () => { + const results = filteredResultsBySelection({ filterKey: 'video', results: mockResults }); + + expect(results.length).toEqual(2); + }); + + it('returns only "course_outline"-typed elements when the key value "document" is given', () => { + const results = filteredResultsBySelection({ filterKey: 'document', results: mockResults }); + + expect(results.length).toEqual(1); + }); + + it('returns only "text"-typed elements when the key value "text" is given', () => { + const results = filteredResultsBySelection({ filterKey: 'text', results: mockResults }); + + expect(results.length).toEqual(3); + }); + }); + + describe('', () => { + it('should render', async () => { + await render(); + + await waitFor(() => { + expect(screen.queryByTestId('courseware-search-results-tabs')).toBeInTheDocument(); + expect(screen.queryByText(/All content/)).toBeInTheDocument(); + expect(screen.queryByText(/Course outline/)).toBeInTheDocument(); + expect(screen.queryByText(/Text/)).toBeInTheDocument(); + expect(screen.queryByText(/Video/)).toBeInTheDocument(); + }); + }); + + it('should not render if no results are provided', async () => { + await render(); + + await waitFor(() => { + expect(screen.queryByTestId('courseware-search-results-tabs')).not.toBeInTheDocument(); + expect(screen.queryByText(/All content/)).not.toBeInTheDocument(); + expect(screen.queryByText(/Course outline/)).not.toBeInTheDocument(); + expect(screen.queryByText(/Text/)).not.toBeInTheDocument(); + expect(screen.queryByText(/Video/)).not.toBeInTheDocument(); + }); + }); + }); +}); diff --git a/src/course-home/courseware-search/CoursewareSearch.jsx b/src/course-home/courseware-search/CoursewareSearch.jsx index 83195d6f2c..cfdc32e4b5 100644 --- a/src/course-home/courseware-search/CoursewareSearch.jsx +++ b/src/course-home/courseware-search/CoursewareSearch.jsx @@ -10,7 +10,7 @@ import { useElementBoundingBox, useLockScroll } from './hooks'; import messages from './messages'; import CoursewareSearchForm from './CoursewareSearchForm'; -import CoursewareSearchResults from './CoursewareSearchResults'; +import CoursewareSearchResultsFilterContainer from './CoursewareResultsFilter'; import mockedData from './test-data/mockedResults'; const CoursewareSearch = ({ intl, ...sectionProps }) => { @@ -50,9 +50,7 @@ const CoursewareSearch = ({ intl, ...sectionProps }) => { onSubmit={handleSubmit} placeholder={intl.formatMessage(messages.searchBarPlaceholderText)} /> - {results !== undefined ? ( - - ) : null} + diff --git a/src/course-home/courseware-search/CoursewareSearchResult.PropTypeDefinition.jsx b/src/course-home/courseware-search/CoursewareSearchResult.PropTypeDefinition.jsx new file mode 100644 index 0000000000..ae55aaef25 --- /dev/null +++ b/src/course-home/courseware-search/CoursewareSearchResult.PropTypeDefinition.jsx @@ -0,0 +1,12 @@ +import PropTypes from 'prop-types'; + +export default { + results: PropTypes.arrayOf(PropTypes.objectOf({ + title: PropTypes.string.isRequired, + href: PropTypes.string.isRequired, + type: PropTypes.string, + breadcrumbs: PropTypes.arrayOf(PropTypes.string), + contentMatches: PropTypes.number, + isExternal: PropTypes.bool, + })), +};