+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis semper rutrum odio quis congue.
+ Duis sodales nibh et sapien elementum fermentum. Quisque magna urna, gravida at gravida et,
+ ultricies vel massa.Aliquam in vehicula dolor, id scelerisque felis.
+ Morbi posuere scelerisque tincidunt. Proin et gravida tortor. Vestibulum vel orci vulputate,
+ gravida justo eu, varius dolor. Etiam viverra diam sed est tincidunt, et aliquam est efficitur.
+ Donec imperdiet eros quis est condimentum faucibus.
+
+
+ In mattis, tellus ut lacinia viverra, ligula ex sagittis ex, sed mollis ex enim ut velit.
+ Nunc elementum, risus eget feugiat scelerisque, sapien felis laoreet nisl, ut pharetra neque
+ lorem a elit. Maecenas elementum, metus fringilla suscipit imperdiet, mi nunc efficitur elit,
+ sed consequat massa magna sit amet dui. Curabitur ultrices nisi vel lorem scelerisque, pharetra
+ luctus nunc pulvinar. Morbi aliquam ante eget arcu condimentum consectetur. Fusce faucibus lacus
+ sed pretium ultrices. Curabitur neque lacus, elementum convallis augue placerat, gravida
+ scelerisque ipsum. Donec bibendum lectus id ullamcorper sodales. Integer quis ante facilisis erat
+ maximus viverra. Nunc rutrum posuere lectus, aliquam congue odio blandit nec. Phasellus placerat,
+ magna non bibendum lacinia, tortor orci vulputate dui, vitae imperdiet turpis dui nec tortor.
+ Praesent porttitor mollis diam ut gravida. Praesent vitae felis dignissim sem accumsan dignissim.
+ Fusce ullamcorper bibendum ante ac pellentesque. Aliquam sed leo vel leo pellentesque cursus a at risus.
+ Donec sollicitudin maximus diam, sit amet molestie sapien commodo at.
+
+
+ Cras ornare pulvinar est id rhoncus. Aenean ut risus magna. Fusce cursus pulvinar dui ut egestas.
+ Quisque condimentum risus non mi sagittis, eu facilisis enim hendrerit. Integer faucibus dapibus rutrum.
+ Nullam vitae mollis tortor, eu lacinia mi. Nunc commodo ex id eros hendrerit, vel interdum augue tristique.
+ Suspendisse ullamcorper, purus in vestibulum auctor, justo nisi finibus dolor,
+ nec dignissim arcu enim a augue.
+
+
+ Fusce vel libero odio. Orci varius natoque penatibus et magnis dis parturient montes,
+ nascetur ridiculus mus. Pellentesque at varius turpis. Ut pulvinar efficitur congue. Vivamus cursus,
+ purus at aliquet malesuada, felis quam blandit dolor, a interdum justo est semper augue.
+ In eu lectus sit amet est pellentesque porta vel eget magna. Morbi sollicitudin turpis vitae faucibus
+ pulvinar. Etiam placerat pulvinar porta.
+
+
+ Suspendisse mattis eget felis non sagittis. Nulla facilisi. In bibendum cursus purus, non venenatis tellus
+ dignissim sit amet. Phasellus volutpat ipsum turpis, non imperdiet nisi elementum a. Nunc mollis, sapien
+ cursus vehicula consectetur, nunc turpis pulvinar mauris, at varius justo mi egestas nisi. Fusce semper
+ sapien in orci rhoncus ornare. Donec maximus mi eu pulvinar convallis.
+
+
+ Nullam tortor sem, hendrerit eu sapien ac, venenatis rhoncus ligula. Donec nibh leo, venenatis sed interdum
+ ac, pharetra sed nibh. Orci varius natoque penatibus et magnis dis parturient montes,
+ nascetur ridiculus mus. Sed congue risus eu mattis condimentum. In id nulla sit amet magna suscipit
+ consectetur. Nullam vitae augue felis. In consequat tempus diam, a eleifend ante bibendum ac.
+ Vivamus mi orci, fermentum ac viverra quis, tristique a ipsum. Morbi imperdiet porta sem, in sollicitudin
+ risus dignissim at. Nulla dapibus iaculis vestibulum.
+
+
+
+
);
};
diff --git a/src/course-home/courseware-search/CoursewareSearch.test.jsx b/src/course-home/courseware-search/CoursewareSearch.test.jsx
index a84f7ef685..276ff1a194 100644
--- a/src/course-home/courseware-search/CoursewareSearch.test.jsx
+++ b/src/course-home/courseware-search/CoursewareSearch.test.jsx
@@ -1,18 +1,27 @@
import React from 'react';
import {
- act,
+ fireEvent,
initializeMockApp,
render,
screen,
- waitFor,
} from '../../setupTest';
-import { fetchCoursewareSearchSettings } from '../data/thunks';
import { CoursewareSearch } from './index';
+import { setShowSearch } from '../data/slice';
+import { useElementBoundingBox, useLockScroll } from './hooks';
-jest.mock('../data/thunks');
+const mockDispatch = jest.fn();
-function renderComponent() {
- const { container } = render();
+jest.mock('./hooks');
+jest.mock('../data/slice');
+jest.mock('react-redux', () => ({
+ ...jest.requireActual('react-redux'),
+ useDispatch: () => mockDispatch,
+}));
+
+const tabsTopPosition = 128;
+
+function renderComponent(props = {}) {
+ const { container } = render();
return container;
}
@@ -21,26 +30,55 @@ describe('CoursewareSearch', () => {
initializeMockApp();
});
- beforeEach(() => {
+ afterEach(() => {
jest.clearAllMocks();
});
- it('Should not render when the waffle flag is disabled', async () => {
- fetchCoursewareSearchSettings.mockImplementation(() => Promise.resolve({ enabled: false }));
+ describe('when rendering normally', () => {
+ beforeAll(() => {
+ useElementBoundingBox.mockImplementation(() => ({ top: tabsTopPosition }));
+ });
+
+ beforeEach(() => {
+ renderComponent();
+ });
+
+ it('Should use useElementBoundingBox() and useLockScroll() hooks', () => {
+ expect(useElementBoundingBox).toBeCalledTimes(1);
+ expect(useLockScroll).toBeCalledTimes(1);
+ });
- await act(async () => renderComponent());
- await waitFor(() => {
- expect(fetchCoursewareSearchSettings).toHaveBeenCalledTimes(1);
- expect(screen.queryByTestId('courseware-search-button')).not.toBeInTheDocument();
+ it('Should have a "--modal-top-position" CSS variable matching the CourseTabsNavigation top position', () => {
+ 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);
});
});
- it('Should render when the waffle flag is enabled', async () => {
- fetchCoursewareSearchSettings.mockImplementation(() => Promise.resolve({ enabled: true }));
- await act(async () => renderComponent());
- await waitFor(() => {
- expect(fetchCoursewareSearchSettings).toHaveBeenCalledTimes(1);
- expect(screen.queryByTestId('courseware-search-button')).toBeInTheDocument();
+ describe('when CourseTabsNavigation is not present', () => {
+ it('Should use "--modal-top-position: 0" if nce element is not present', () => {
+ useElementBoundingBox.mockImplementation(() => undefined);
+ renderComponent();
+
+ const section = screen.getByTestId('courseware-search-section');
+ expect(section.style.getPropertyValue('--modal-top-position')).toBe('0');
+ });
+ });
+
+ describe('when passing extra props', () => {
+ it('Should pass on extra props to section element', () => {
+ renderComponent({ foo: 'bar' });
+
+ const section = screen.getByTestId('courseware-search-section');
+ expect(section).toHaveAttribute('foo', 'bar');
});
});
});
diff --git a/src/course-home/courseware-search/CoursewareSearchToggle.jsx b/src/course-home/courseware-search/CoursewareSearchToggle.jsx
new file mode 100644
index 0000000000..0a5707bfca
--- /dev/null
+++ b/src/course-home/courseware-search/CoursewareSearchToggle.jsx
@@ -0,0 +1,38 @@
+import React from 'react';
+import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
+import { Button, Icon } from '@edx/paragon';
+import { Search } from '@edx/paragon/icons';
+import { useDispatch } from 'react-redux';
+import { setShowSearch } from '../data/slice';
+import messages from './messages';
+import { useCoursewareSearchFeatureFlag } from './hooks';
+
+const CoursewareSearchToggle = ({
+ intl,
+}) => {
+ const dispatch = useDispatch();
+ const enabled = useCoursewareSearchFeatureFlag();
+
+ if (!enabled) { return null; }
+
+ return (
+