Skip to content

Commit

Permalink
fix: update tests
Browse files Browse the repository at this point in the history
  • Loading branch information
germanolleunlp committed Nov 8, 2023
1 parent 71e5e50 commit a4b24bb
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 578 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const CoursewareSearchResultsFilter = ({ intl }) => {
const { courseId } = useParams();
const lastSearch = useModel('contentSearchResults', courseId);

if (!lastSearch || !lastSearch.results.length) { return null; }
if (!lastSearch || !lastSearch?.results?.length) { return null; }

const { total, results } = lastSearch;

Expand Down Expand Up @@ -47,7 +47,6 @@ export const CoursewareSearchResultsFilter = ({ intl }) => {
{filters.map(({ key, label }) => (
<Tab key={key} eventKey={key} title={getFilterTitle(key, label)}>
<CoursewareSearchResults
intl={intl}
results={filteredResultsBySelection({ key, results })}
/>
</Tab>
Expand Down
94 changes: 63 additions & 31 deletions src/course-home/courseware-search/CoursewareResultsFilter.test.jsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,64 @@
import React from 'react';
import { AppProvider } from '@edx/frontend-platform/react';
import { Route, Routes } from 'react-router-dom';
import { history } from '@edx/frontend-platform';
import {
initializeMockApp,
render,
screen,
waitFor,
} from '../../setupTest';
import { CoursewareSearchResultsFilter, filteredResultsBySelection } from './CoursewareResultsFilter';
import initializeStore from '../../store';
import { useModel } from '../../generic/model-store';

jest.mock('../../generic/model-store', () => ({
useModel: jest.fn(),
}));

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' },
{
id: 'video-1', type: 'video', title: 'video_title', score: 3, contentHits: 1, url: '/video-1', location: ['path1', 'path2'],
},
{
id: 'video-2', type: 'video', title: 'video_title2', score: 2, contentHits: 1, url: '/video-2', location: ['path1', 'path2'],
},
{
id: 'document-1', type: 'document', title: 'document_title', score: 3, contentHits: 1, url: '/document-1', location: ['path1', 'path2'],
},
{
id: 'text-1', type: 'text', title: 'text_title1', score: 3, contentHits: 1, url: '/text-1', location: ['path1', 'path2'],
},
{
id: 'text-2', type: 'text', title: 'text_title2', score: 2, contentHits: 1, url: '/text-2', location: ['path1', 'path2'],
},
{
id: 'text-3', type: 'text', title: 'text_title3', score: 1, contentHits: 1, url: '/text-3', location: ['path1', 'path2'],
},
];

const decodedCourseId = 'course-v1:edX+DemoX+Demo_Course';
const decodedSequenceId = 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@edx_introduction';
const decodedUnitId = 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_0270f6de40fc';
const pathname = `/course/${decodedCourseId}/${decodedSequenceId}/${decodedUnitId}`;

const intl = {
formatMessage: (message) => message?.defaultMessage || '',
};

function renderComponent(props = {}) {
const store = initializeStore();
history.push(pathname);
const { container } = render(
<AppProvider store={store}>
<Routes>
<Route path="/course/:courseId/:sequenceId/:unitId" element={<CoursewareSearchResultsFilter intl={intl} {...props} />} />
</Routes>
</AppProvider>,
);
return container;
}

describe('CoursewareSearchResultsFilter', () => {
beforeAll(initializeMockApp);

Expand All @@ -32,53 +75,42 @@ describe('CoursewareSearchResultsFilter', () => {
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 });
const results = filteredResultsBySelection({ key: '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 });
const results = filteredResultsBySelection({ key: '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 });
const results = filteredResultsBySelection({ key: 'text', results: mockResults });

expect(results.length).toEqual(3);
});
});

describe('</CoursewareSearchResultsFilter />', () => {
it('should render', async () => {
await render(<CoursewareSearchResultsFilter results={mockResults} />);
beforeEach(() => {
jest.clearAllMocks();
});

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 render', async () => {
useModel.mockReturnValue({
total: 6,
results: mockResults,
filters: [],
});
});

it('should not render if no results are provided', async () => {
await render(<CoursewareSearchResultsFilter results={[]} />);
await renderComponent();

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();
expect(screen.queryByTestId('courseware-search-results-tabs')).toBeInTheDocument();
expect(screen.queryByText(/All content/)).toBeInTheDocument();
});
});
});
Expand Down
5 changes: 3 additions & 2 deletions src/course-home/courseware-search/CoursewareSearch.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import CoursewareSearchResultsFilterContainer from './CoursewareResultsFilter';
import { updateModel, useModel } from '../../generic/model-store';
import { searchCourseContent } from '../data/thunks';

const CoursewareSearch = ({ intl, ...sectionProps }) => {
const CoursewareSearch = ({ intl }) => {
const { courseId } = useParams();
const dispatch = useDispatch();
const { org } = useModel('courseHomeMeta', courseId);
Expand Down Expand Up @@ -88,7 +88,7 @@ const CoursewareSearch = ({ intl, ...sectionProps }) => {
}

return (
<section className="courseware-search" style={{ '--modal-top-position': top }} data-testid="courseware-search-section" {...sectionProps}>
<section className="courseware-search" style={{ '--modal-top-position': top }} data-testid="courseware-search-section">
<div className="courseware-search__close">
<Button
variant="tertiary"
Expand Down Expand Up @@ -138,6 +138,7 @@ const CoursewareSearch = ({ intl, ...sectionProps }) => {
</section>
);
};

CoursewareSearch.propTypes = {
intl: intlShape.isRequired,
};
Expand Down
73 changes: 47 additions & 26 deletions src/course-home/courseware-search/CoursewareSearch.test.jsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,61 @@
import React from 'react';
import { history } from '@edx/frontend-platform';
import { AppProvider } from '@edx/frontend-platform/react';
import { Route, Routes } from 'react-router-dom';
import {
fireEvent,
initializeMockApp,
render,
screen,
} from '../../setupTest';
import { CoursewareSearch } from './index';
import { setShowSearch } from '../data/slice';
import { useElementBoundingBox, useLockScroll } from './hooks';
import initializeStore from '../../store';
import { useModel } from '../../generic/model-store';

const mockDispatch = jest.fn();

jest.mock('./hooks');
jest.mock('../data/slice');
jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
useDispatch: () => mockDispatch,
jest.mock('../../generic/model-store', () => ({
useModel: jest.fn(),
}));

const decodedCourseId = 'course-v1:edX+DemoX+Demo_Course';
const decodedSequenceId = 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@edx_introduction';
const decodedUnitId = 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_0270f6de40fc';
const pathname = `/course/${decodedCourseId}/${decodedSequenceId}/${decodedUnitId}`;

const tabsTopPosition = 128;

const defaultProps = {
org: 'edX',
loading: false,
searchKeyword: '',
errors: undefined,
total: 0,
};

const intl = {
formatMessage: (message) => message?.defaultMessage || '',
};

function renderComponent(props = {}) {
const { container } = render(<CoursewareSearch {...props} />);
const store = initializeStore();
history.push(pathname);
const { container } = render(
<AppProvider store={store}>
<Routes>
<Route path="/course/:courseId/:sequenceId/:unitId" element={<CoursewareSearch intl={intl} {...props} />} />
</Routes>
</AppProvider>,
);
return container;
}

const mockModels = ((props = defaultProps) => {
useModel.mockReturnValue(props);
});

describe('CoursewareSearch', () => {
beforeAll(async () => {
initializeMockApp();
});
beforeAll(initializeMockApp);

afterEach(() => {
beforeEach(() => {
jest.clearAllMocks();
});

Expand All @@ -39,33 +64,28 @@ describe('CoursewareSearch', () => {
useElementBoundingBox.mockImplementation(() => ({ top: tabsTopPosition }));
});

beforeEach(() => {
it('Should use useElementBoundingBox() and useLockScroll() hooks', () => {
mockModels();
renderComponent();
});

it('Should use useElementBoundingBox() and useLockScroll() hooks', () => {
expect(useElementBoundingBox).toBeCalledTimes(1);
expect(useLockScroll).toBeCalledTimes(1);
});

it('Should have a "--modal-top-position" CSS variable matching the CourseTabsNavigation top position', () => {
mockModels();
renderComponent();

const section = screen.getByTestId('courseware-search-section');
expect(section.style.getPropertyValue('--modal-top-position')).toBe(`${tabsTopPosition}px`);
});

it('Should dispatch setShowSearch(true) when clicking the close button', () => {
const button = screen.getByTestId('courseware-search-close-button');
fireEvent.click(button);

expect(mockDispatch).toHaveBeenCalledTimes(1);
expect(setShowSearch).toHaveBeenCalledTimes(1);
expect(setShowSearch).toHaveBeenCalledWith(false);
});
});

describe('when CourseTabsNavigation is not present', () => {
it('Should use "--modal-top-position: 0" if nce element is not present', () => {
useElementBoundingBox.mockImplementation(() => undefined);

mockModels();
renderComponent();

const section = screen.getByTestId('courseware-search-section');
Expand All @@ -75,6 +95,7 @@ describe('CoursewareSearch', () => {

describe('when passing extra props', () => {
it('Should pass on extra props to section element', () => {
mockModels();
renderComponent({ foo: 'bar' });

const section = screen.getByTestId('courseware-search-section');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const iconTypeMapping = {
text: TextFields,
video: VideoCamera,
};

const defaultIcon = Article;

const CoursewareSearchResults = ({ results }) => {
Expand Down Expand Up @@ -57,7 +58,7 @@ const CoursewareSearchResults = ({ results }) => {
// The breadcrumbs are not expected to change.
// eslint-disable-next-line react/no-array-index-key
location.map((bc, i) => (<li key={`${i}:${bc}`}><div>{bc}</div></li>))
}
}
</ul>
) : null}
</div>
Expand Down
Loading

0 comments on commit a4b24bb

Please sign in to comment.