From c3eaded9679f01d3134238bb5b2dcb24f1cdc51e Mon Sep 17 00:00:00 2001 From: Marcos Date: Wed, 4 Oct 2023 11:01:33 -0300 Subject: [PATCH] feat: added waffle flag state for Courseware Search --- .../courseware-search/CoursewareSearch.jsx | 26 ++++++++++ .../CoursewareSearch.test.jsx | 49 +++++++++++++++++++ src/course-home/courseware-search/index.js | 2 + src/course-home/courseware-search/messages.js | 11 +++++ src/course-home/data/api.js | 6 +++ src/course-home/data/thunks.js | 11 +++++ src/course-tabs/CourseTabsNavigation.jsx | 4 ++ src/course-tabs/CourseTabsNavigation.test.jsx | 19 +++++++ 8 files changed, 128 insertions(+) create mode 100644 src/course-home/courseware-search/CoursewareSearch.jsx create mode 100644 src/course-home/courseware-search/CoursewareSearch.test.jsx create mode 100644 src/course-home/courseware-search/index.js create mode 100644 src/course-home/courseware-search/messages.js diff --git a/src/course-home/courseware-search/CoursewareSearch.jsx b/src/course-home/courseware-search/CoursewareSearch.jsx new file mode 100644 index 0000000000..8603c03d4b --- /dev/null +++ b/src/course-home/courseware-search/CoursewareSearch.jsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { useParams } from 'react-router-dom'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { Button, Icon } from '@edx/paragon'; +import { Search } from '@edx/paragon/icons'; +import { useModel } from '../../generic/model-store'; +import messages from './messages'; + +const CoursewareSearch = ({ intl, ...rest }) => { + const { courseId } = useParams(); + const { enabled } = useModel('coursewareSearch', courseId); + + if (!enabled) { return null; } + + return ( + + ); +}; + +CoursewareSearch.propTypes = { + intl: intlShape.isRequired, +}; + +export default injectIntl(CoursewareSearch); diff --git a/src/course-home/courseware-search/CoursewareSearch.test.jsx b/src/course-home/courseware-search/CoursewareSearch.test.jsx new file mode 100644 index 0000000000..8ae7fa491b --- /dev/null +++ b/src/course-home/courseware-search/CoursewareSearch.test.jsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { + initializeMockApp, + render, + screen, +} from '../../setupTest'; +import { useModel } from '../../generic/model-store'; +import { CoursewareSearch } from './index'; + +jest.mock('../../generic/model-store', () => ({ + useModel: jest.fn(), +})); + +function renderComponent() { + const { container } = render(); + return container; +} + +describe('CoursewareSearch', () => { + beforeAll(async () => { + initializeMockApp(); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('When rendered by default', () => { + beforeEach(() => { + useModel.mockReturnValue({ enabled: false }); + renderComponent(); + }); + + it('Should not show', () => { + expect(screen.queryByTestId('courseware-search-button')).not.toBeInTheDocument(); + }); + }); + + describe('When rendered with the waffle flag enabled', () => { + beforeEach(() => { + useModel.mockReturnValue({ enabled: true }); + renderComponent(); + }); + + it('Should show', () => { + expect(screen.getByTestId('courseware-search-button')).toBeInTheDocument(); + }); + }); +}); diff --git a/src/course-home/courseware-search/index.js b/src/course-home/courseware-search/index.js new file mode 100644 index 0000000000..c773bbcbed --- /dev/null +++ b/src/course-home/courseware-search/index.js @@ -0,0 +1,2 @@ +/* eslint-disable import/prefer-default-export */ +export { default as CoursewareSearch } from './CoursewareSearch'; diff --git a/src/course-home/courseware-search/messages.js b/src/course-home/courseware-search/messages.js new file mode 100644 index 0000000000..a857249429 --- /dev/null +++ b/src/course-home/courseware-search/messages.js @@ -0,0 +1,11 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + searchOpenAction: { + id: 'learn.coursewareSerch.openAction', + defaultMessage: 'Search within this course', + description: 'Aria-label for a button that will pop up Courseware Search.', + }, +}); + +export default messages; diff --git a/src/course-home/data/api.js b/src/course-home/data/api.js index b32973ef47..7b3df169b5 100644 --- a/src/course-home/data/api.js +++ b/src/course-home/data/api.js @@ -445,3 +445,9 @@ export async function unsubscribeFromCourseGoal(token) { return getAuthenticatedHttpClient().post(url.href) .then(res => camelCaseObject(res)); } + +export async function getCoursewareSearchEnabledFlag(courseId) { + const url = `${getConfig().LMS_BASE_URL}/courses/${courseId}/courseware-search/enabled/`; + const { data } = await getAuthenticatedHttpClient().get(url); + return { enabled: data.enabled || false }; +} diff --git a/src/course-home/data/thunks.js b/src/course-home/data/thunks.js index b7c1c3f9a2..2c461ec1a6 100644 --- a/src/course-home/data/thunks.js +++ b/src/course-home/data/thunks.js @@ -12,6 +12,7 @@ import { postDismissWelcomeMessage, postRequestCert, getLiveTabIframe, + getCoursewareSearchEnabledFlag, } from './api'; import { @@ -52,6 +53,16 @@ export function fetchTab(courseId, tab, getTabData, targetUserId) { }, })); } + + const { enabled } = await getCoursewareSearchEnabledFlag(courseId); + dispatch(addModel({ + modelType: 'coursewareSearch', + model: { + id: courseId, + enabled, + }, + })); + // Disable the access-denied path for now - it caused a regression if (!courseHomeCourseMetadata.courseAccess.hasAccess) { dispatch(fetchTabDenied({ courseId })); diff --git a/src/course-tabs/CourseTabsNavigation.jsx b/src/course-tabs/CourseTabsNavigation.jsx index 838a558ad6..1f235ce317 100644 --- a/src/course-tabs/CourseTabsNavigation.jsx +++ b/src/course-tabs/CourseTabsNavigation.jsx @@ -5,11 +5,15 @@ import classNames from 'classnames'; import messages from './messages'; import Tabs from '../generic/tabs/Tabs'; +import { CoursewareSearch } from '../course-home/courseware-search'; const CourseTabsNavigation = ({ activeTabSlug, className, tabs, intl, }) => (
+
+ +
({ + useModel: jest.fn(), +})); describe('Course Tabs Navigation', () => { beforeAll(async () => { + useModel.mock((model) => (model === 'coursewareSearch' ? ({ enabled: false }) : undefined)); initializeMockApp(); }); @@ -29,4 +35,17 @@ describe('Course Tabs Navigation', () => { expect(screen.getByRole('link', { name: tabs[1].title })).toHaveAttribute('href', tabs[1].url); expect(screen.getByRole('link', { name: tabs[1].title })).not.toHaveClass('active'); }); + + it('does not render the CoursewareSearch component by default', () => { + render(); + + expect(screen.queryByTestId('courseware-search').not.toBeInTheDocument()); + }); + + it('renders the CoursewareSearch component if the waffle flag is enabled', () => { + useModel.mock((model) => (model === 'coursewareSearch' ? ({ enabled: true }) : undefined)); + render(); + + expect(screen.getByTestId('courseware-search').toBeInTheDocument()); + }); });