diff --git a/src/CourseAuthoringPage.jsx b/src/CourseAuthoringPage.jsx
index 84662e2ac3..6b8e968277 100644
--- a/src/CourseAuthoringPage.jsx
+++ b/src/CourseAuthoringPage.jsx
@@ -18,8 +18,6 @@ import Loading from './generic/Loading';
const CourseAuthoringPage = ({ courseId, children }) => {
const dispatch = useDispatch();
- const STORE = useSelector(state => state);
- console.log('STORE', STORE);
useEffect(() => {
dispatch(fetchCourseDetail(courseId));
diff --git a/src/course-checklist/ChecklistSection/ChecklistItemBody.jsx b/src/course-checklist/ChecklistSection/ChecklistItemBody.jsx
index 6625333919..47c7bd7063 100644
--- a/src/course-checklist/ChecklistSection/ChecklistItemBody.jsx
+++ b/src/course-checklist/ChecklistSection/ChecklistItemBody.jsx
@@ -10,6 +10,7 @@ import {
import { useSelector } from 'react-redux';
import { CheckCircle, RadioButtonUnchecked } from '@openedx/paragon/icons';
+import { getWaffleFlags } from '../../data/selectors';
import messages from './messages';
const ChecklistItemBody = ({
@@ -20,7 +21,7 @@ const ChecklistItemBody = ({
// injected
intl,
}) => {
- const waffleFlags = useSelector(state => state.courseDetail.waffleFlags);
+ const waffleFlags = useSelector(getWaffleFlags);
const navigate = useNavigate();
const handleClick = (e, url) => {
diff --git a/src/course-checklist/ChecklistSection/ChecklistSection.test.jsx b/src/course-checklist/ChecklistSection/ChecklistSection.test.jsx
index 61a87d60a8..98c49812e2 100644
--- a/src/course-checklist/ChecklistSection/ChecklistSection.test.jsx
+++ b/src/course-checklist/ChecklistSection/ChecklistSection.test.jsx
@@ -23,53 +23,36 @@ const defaultProps = {
checkId: 'welcomeMessage',
isCompleted: false,
updateLink: 'https://example.com/update',
- intl: {
- formatMessage: jest.fn(({ defaultMessage }) => defaultMessage),
- },
};
-const waffleFlags = {
- ENABLE_NEW_COURSE_UPDATES_PAGE: false,
+const renderComponent = (props = {}, mockWaffleFlags = { useNewUpdatesPage: false }) => {
+ useSelector.mockReturnValue(mockWaffleFlags);
+ return render(
+
+
+ ,
+ );
};
describe('ChecklistItemBody', () => {
- beforeEach(() => {
- useSelector.mockReturnValue({ waffleFlags });
- });
-
afterEach(() => {
jest.clearAllMocks();
});
it('renders uncompleted icon when isCompleted is false', () => {
- render(
-
-
- ,
- );
-
+ renderComponent();
const uncompletedIcon = screen.getByTestId('uncompleted-icon');
expect(uncompletedIcon).toBeInTheDocument();
});
it('renders completed icon when isCompleted is true', () => {
- render(
-
-
- ,
- );
-
+ renderComponent({ isCompleted: true });
const completedIcon = screen.getByTestId('completed-icon');
expect(completedIcon).toBeInTheDocument();
});
it('renders short and long descriptions based on checkId', () => {
- render(
-
-
- ,
- );
-
+ renderComponent();
const shortDescription = screen.getByText(messages.welcomeMessageShortDescription.defaultMessage);
const longDescription = screen.getByText(messages.welcomeMessageLongDescription.defaultMessage);
@@ -78,38 +61,21 @@ describe('ChecklistItemBody', () => {
});
it('renders update hyperlink when updateLink is provided', () => {
- render(
-
-
- ,
- );
-
+ renderComponent();
const updateLink = screen.getByTestId('update-hyperlink');
expect(updateLink).toBeInTheDocument();
});
- it('navigates to internal course page if ENABLE_NEW_COURSE_UPDATES_PAGE flag is enabled', () => {
- useSelector.mockReturnValue({ waffleFlags: { ENABLE_NEW_COURSE_UPDATES_PAGE: true } });
-
- render(
-
-
- ,
- );
-
+ it('navigates to internal course page if useNewUpdatesPage flag is enabled', () => {
+ renderComponent({}, { useNewUpdatesPage: true });
const updateLink = screen.getByTestId('update-hyperlink');
fireEvent.click(updateLink);
expect(mockNavigate).toHaveBeenCalledWith(`/course/${defaultProps.courseId}/course_info`);
});
- it('redirects to external link if ENABLE_NEW_COURSE_UPDATES_PAGE flag is disabled', () => {
- render(
-
-
- ,
- );
-
+ it('redirects to external link if useNewUpdatesPage flag is disabled', () => {
+ renderComponent();
const updateLink = screen.getByTestId('update-hyperlink');
fireEvent.click(updateLink);
diff --git a/src/course-outline/hooks.jsx b/src/course-outline/hooks.jsx
index 0f1254d2cf..20baabfb6b 100644
--- a/src/course-outline/hooks.jsx
+++ b/src/course-outline/hooks.jsx
@@ -6,6 +6,7 @@ import { getConfig } from '@edx/frontend-platform';
import { copyToClipboard } from '../generic/data/thunks';
import { getSavingStatus as getGenericSavingStatus } from '../generic/data/selectors';
+import { getWaffleFlags } from '../data/selectors';
import { RequestStatus } from '../data/constants';
import { COURSE_BLOCK_NAMES } from './constants';
import {
@@ -58,7 +59,7 @@ import {
const useCourseOutline = ({ courseId }) => {
const dispatch = useDispatch();
const navigate = useNavigate();
- const waffleFlags = useSelector(state => state.courseDetail.waffleFlags);
+ const waffleFlags = useSelector(getWaffleFlags);
const {
reindexLink,
diff --git a/src/course-outline/status-bar/StatusBar.jsx b/src/course-outline/status-bar/StatusBar.jsx
index a2ad214b2c..cdfcd6b182 100644
--- a/src/course-outline/status-bar/StatusBar.jsx
+++ b/src/course-outline/status-bar/StatusBar.jsx
@@ -13,6 +13,7 @@ import { useSelector } from 'react-redux';
import { ContentTagsDrawerSheet } from '../../content-tags-drawer';
import TagCount from '../../generic/tag-count';
import { useHelpUrls } from '../../help-urls/hooks';
+import { getWaffleFlags } from '../../data/selectors';
import { VIDEO_SHARING_OPTIONS } from '../constants';
import { useContentTagsCount } from '../../generic/data/apiHooks';
import messages from './messages';
@@ -45,7 +46,7 @@ const StatusBar = ({
}) => {
const intl = useIntl();
const { config } = useContext(AppContext);
- const waffleFlags = useSelector(state => state.courseDetail.waffleFlags);
+ const waffleFlags = useSelector(getWaffleFlags);
const {
courseReleaseDate,
diff --git a/src/course-unit/breadcrumbs/Breadcrumbs.jsx b/src/course-unit/breadcrumbs/Breadcrumbs.jsx
index 54e18bdabd..244006b807 100644
--- a/src/course-unit/breadcrumbs/Breadcrumbs.jsx
+++ b/src/course-unit/breadcrumbs/Breadcrumbs.jsx
@@ -8,6 +8,7 @@ import {
} from '@openedx/paragon/icons';
import { getConfig } from '@edx/frontend-platform';
+import { getWaffleFlags } from '../../data/selectors';
import { createCorrectInternalRoute } from '../../utils';
import { getCourseSectionVertical } from '../data/selectors';
import messages from './messages';
@@ -17,7 +18,7 @@ const Breadcrumbs = () => {
const { ancestorXblocks } = useSelector(getCourseSectionVertical);
const [section, subsection] = ancestorXblocks ?? [];
const navigate = useNavigate();
- const waffleFlags = useSelector(state => state.courseDetail.waffleFlags);
+ const waffleFlags = useSelector(getWaffleFlags);
const handleClick = (e, url) => {
e.preventDefault();
@@ -44,7 +45,7 @@ const Breadcrumbs = () => {
{section.children.map(({ url, displayName }) => (
handleClick(e, createCorrectInternalRoute(`${url}`))}
+ onClick={(e) => handleClick(e, createCorrectInternalRoute(url))}
className="small"
data-testid="breadcrumbs-section-dropdown-item"
>
@@ -73,7 +74,7 @@ const Breadcrumbs = () => {
{subsection.children.map(({ url, displayName }) => (
handleClick(e, createCorrectInternalRoute(`${url}`))}
+ onClick={(e) => handleClick(e, createCorrectInternalRoute(url))}
className="small"
data-testid="breadcrumbs-subsection-dropdown-item"
>
diff --git a/src/data/api.js b/src/data/api.js
index 22072ae160..718e206f50 100644
--- a/src/data/api.js
+++ b/src/data/api.js
@@ -17,27 +17,11 @@ export async function getCourseDetail(courseId, username) {
}
export async function getWaffleFlags(courseId) {
- // const { data } = await getAuthenticatedHttpClient()
- // .get(`${getConfig().STUDIO_BASE_URL}/api/contentstore/v1/course_waffle_flags`);
-
- const data = {
- use_new_home_page: true,
- use_new_custom_pages: true,
- use_new_schedule_details_page: true,
- use_new_advanced_settings_page: true,
- use_new_grading_page: true,
- use_new_updates_page: true,
- use_new_import_page: true,
- use_new_export_page: true,
- use_new_files_uploads_page: true,
- use_new_video_uploads_page: true,
- use_new_course_outline_page: true,
- use_new_unit_page: true,
- use_new_course_team_page: true,
- use_new_certificates_page: true,
- use_new_textbooks_page: true,
- use_new_group_configurations_page: true,
- };
+ const apiUrl = courseId
+ ? `${getConfig().STUDIO_BASE_URL}/api/contentstore/v1/course_waffle_flags/${courseId}/`
+ : `${getConfig().STUDIO_BASE_URL}/api/contentstore/v1/course_waffle_flags/`;
+ const { data } = await getAuthenticatedHttpClient()
+ .get(apiUrl);
return normalizeCourseDetail(data);
}
diff --git a/src/data/selectors.js b/src/data/selectors.js
new file mode 100644
index 0000000000..86076bcccb
--- /dev/null
+++ b/src/data/selectors.js
@@ -0,0 +1,2 @@
+// eslint-disable-next-line import/prefer-default-export
+export const getWaffleFlags = (state) => state.courseDetail.waffleFlags;
diff --git a/src/data/thunks.js b/src/data/thunks.js
index 2ae6f426f2..2ce79bb3b5 100644
--- a/src/data/thunks.js
+++ b/src/data/thunks.js
@@ -37,7 +37,6 @@ export function fetchWaffleFlags(courseId) {
try {
const waffleFlags = await getWaffleFlags(courseId);
dispatch(updateStatus({ courseId, status: RequestStatus.SUCCESSFUL }));
- console.log('fetchWaffleFlags thunk', waffleFlags);
dispatch(fetchWaffleFlagsSuccess({ waffleFlags }));
} catch (error) {
if (error.response && error.response.status === 404) {
diff --git a/src/generic/help-sidebar/HelpSidebar.jsx b/src/generic/help-sidebar/HelpSidebar.jsx
index 0017d0512d..cdcfde15b9 100644
--- a/src/generic/help-sidebar/HelpSidebar.jsx
+++ b/src/generic/help-sidebar/HelpSidebar.jsx
@@ -5,7 +5,7 @@ import classNames from 'classnames';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
-import { getStudioHomeData } from '../../studio-home/data/selectors';
+import { getWaffleFlags } from '../../data/selectors';
import { otherLinkURLParams } from './constants';
import messages from './messages';
import HelpSidebarLink from './HelpSidebarLink';
@@ -26,7 +26,7 @@ const HelpSidebar = ({
scheduleAndDetails,
groupConfigurations,
} = otherLinkURLParams;
- const waffleFlags = useSelector(state => state.courseDetail.waffleFlags);
+ const waffleFlags = useSelector(getWaffleFlags);
const showOtherLink = (params) => !pathname.includes(params);
const generateLegacyURL = (urlParameter) => {
diff --git a/src/header/Header.tsx b/src/header/Header.tsx
index a1fe9b3df7..a5070fcf06 100644
--- a/src/header/Header.tsx
+++ b/src/header/Header.tsx
@@ -5,7 +5,7 @@ import { StudioHeader } from '@edx/frontend-component-header';
import { type Container, useToggle } from '@openedx/paragon';
import { generatePath, useHref, useNavigate } from 'react-router-dom';
-import { getStudioHomeData } from '../studio-home/data/selectors';
+import { getWaffleFlags } from '../data/selectors';
import { SearchModal } from '../search-modal';
import { useContentMenuItems, useSettingMenuItems, useToolsMenuItems } from './hooks';
import messages from './messages';
@@ -34,7 +34,7 @@ const Header = ({
const intl = useIntl();
const libraryHref = useHref('/library/:libraryId');
const navigate = useNavigate();
- const waffleFlags = useSelector(state => state.courseDetail.waffleFlags);
+ const waffleFlags = useSelector(getWaffleFlags);
const [isShowSearchModalOpen, openSearchModal, closeSearchModal] = useToggle(false);
diff --git a/src/header/hooks.js b/src/header/hooks.js
index d18a15e121..afd4a3c4fc 100644
--- a/src/header/hooks.js
+++ b/src/header/hooks.js
@@ -1,14 +1,16 @@
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { useSelector } from 'react-redux';
+
import { getPagePath } from '../utils';
+import { getWaffleFlags } from '../data/selectors';
import { getStudioHomeData } from '../studio-home/data/selectors';
import messages from './messages';
export const useContentMenuItems = courseId => {
const intl = useIntl();
const studioBaseUrl = getConfig().STUDIO_BASE_URL;
- const waffleFlags = useSelector(state => state.courseDetail.waffleFlags);
+ const waffleFlags = useSelector(getWaffleFlags);
const items = [
{
@@ -42,7 +44,7 @@ export const useSettingMenuItems = courseId => {
const intl = useIntl();
const studioBaseUrl = getConfig().STUDIO_BASE_URL;
const { canAccessAdvancedSettings } = useSelector(getStudioHomeData);
- const waffleFlags = useSelector(state => state.courseDetail.waffleFlags);
+ const waffleFlags = useSelector(getWaffleFlags);
const items = [
{
@@ -80,7 +82,7 @@ export const useSettingMenuItems = courseId => {
export const useToolsMenuItems = courseId => {
const intl = useIntl();
const studioBaseUrl = getConfig().STUDIO_BASE_URL;
- const waffleFlags = useSelector(state => state.courseDetail.waffleFlags);
+ const waffleFlags = useSelector(getWaffleFlags);
const items = [
{
diff --git a/src/pages-and-resources/pages/PageSettingButton.jsx b/src/pages-and-resources/pages/PageSettingButton.jsx
index 0f20fc0c2d..952f85afc0 100644
--- a/src/pages-and-resources/pages/PageSettingButton.jsx
+++ b/src/pages-and-resources/pages/PageSettingButton.jsx
@@ -7,6 +7,7 @@ import { Icon, IconButton } from '@openedx/paragon';
import { ArrowForward, Settings } from '@openedx/paragon/icons';
import { useNavigate, Link } from 'react-router-dom';
+import { getWaffleFlags } from '../../data/selectors';
import messages from '../messages';
import { PagesAndResourcesContext } from '../PagesAndResourcesProvider';
@@ -19,7 +20,7 @@ const PageSettingButton = ({
const { formatMessage } = useIntl();
const { path: pagesAndResourcesPath } = useContext(PagesAndResourcesContext);
const navigate = useNavigate();
- const waffleFlags = useSelector(state => state.courseDetail.waffleFlags);
+ const waffleFlags = useSelector(getWaffleFlags);
const determineLinkDestination = useMemo(() => {
if (!legacyLink) { return null; }
diff --git a/src/pages-and-resources/pages/PageSettingButton.test.jsx b/src/pages-and-resources/pages/PageSettingButton.test.jsx
index d9a22db14d..6def7abcb7 100644
--- a/src/pages-and-resources/pages/PageSettingButton.test.jsx
+++ b/src/pages-and-resources/pages/PageSettingButton.test.jsx
@@ -1,80 +1,86 @@
import { render, screen } from '@testing-library/react';
+import { useSelector } from 'react-redux';
import { IntlProvider } from '@edx/frontend-platform/i18n';
-import { Provider } from 'react-redux';
-import { MemoryRouter } from 'react-router-dom';
-import configureStore from 'redux-mock-store';
import PageSettingButton from './PageSettingButton';
-import messages from '../messages';
-
-const mockStore = configureStore([]);
-
-const renderComponent = (props, store) => {
- render(
-
-
-
-
-
-
- ,
- );
+
+jest.mock('react-redux', () => ({
+ useSelector: jest.fn(),
+}));
+
+jest.mock('react-router-dom', () => {
+ // eslint-disable-next-line global-require
+ const PropTypes = require('prop-types');
+
+ const Link = ({ children, to }) => {children};
+
+ Link.propTypes = {
+ children: PropTypes.node.isRequired,
+ to: PropTypes.string.isRequired,
+ };
+
+ return {
+ useNavigate: jest.fn(),
+ Link,
+ };
+});
+
+const mockWaffleFlags = {
+ useNewTextbooksPage: true,
+ useNewCustomPages: true,
};
-describe('PageSettingButton', () => {
- let store;
+const defaultProps = {
+ id: 'page_id',
+ courseId: 'course-v1:edX+DemoX+Demo_Course',
+ legacyLink: 'http://legacylink.com/tabs',
+ allowedOperations: { configure: true, enable: true },
+};
+
+const renderComponent = (props = {}) => render(
+
+
+ ,
+);
+describe('PageSettingButton', () => {
beforeEach(() => {
- store = mockStore({
- studioHomeData: {
- waffleFlags: {
- ENABLE_NEW_TEXTBOOKS_PAGE: true,
- ENABLE_NEW_CUSTOM_PAGES: true,
- },
- },
- });
+ useSelector.mockClear();
});
- it('renders a link when legacyLink is provided and matches textbooks condition', () => {
- renderComponent({
- id: 'page_1',
- courseId: 'course_1',
- legacyLink: 'textbooks',
- allowedOperations: {},
- }, store);
+ it('renders the settings button with the new textbooks page link when useNewTextbooksPage is true', () => {
+ useSelector.mockReturnValue(mockWaffleFlags);
+
+ renderComponent({ legacyLink: 'http://legacylink.com/textbooks' });
- expect(screen.getByRole('link')).toHaveAttribute('href', '/textbooks');
+ const linkElement = screen.getByRole('link');
+ expect(linkElement).toHaveAttribute('href', `/course/${defaultProps.courseId}/page-id`);
});
- it('renders a link when legacyLink is provided and matches tabs condition', () => {
- renderComponent({
- id: 'page_2',
- courseId: 'course_2',
- legacyLink: 'tabs',
- allowedOperations: {},
- }, store);
+ it('renders the settings button with the legacy link when useNewTextbooksPage is false', () => {
+ useSelector.mockReturnValue({ ...mockWaffleFlags, useNewTextbooksPage: false });
- expect(screen.getByRole('link')).toHaveAttribute('href', '/tabs');
+ renderComponent({ legacyLink: 'http://legacylink.com/textbooks' });
+
+ const linkElement = screen.getByRole('link');
+ expect(linkElement).toHaveAttribute('href', 'http://legacylink.com/textbooks');
});
- it('renders an IconButton when allowedOperations allows configuration or enabling', () => {
- renderComponent({
- id: 'page_3',
- courseId: 'course_3',
- allowedOperations: { configure: true },
- }, store);
+ it('renders the settings button with the new custom pages link when useNewCustomPages is true', () => {
+ useSelector.mockReturnValue(mockWaffleFlags);
+
+ renderComponent();
- expect(screen.getByRole('button')).toBeInTheDocument();
+ const linkElement = screen.getByRole('link');
+ expect(linkElement).toHaveAttribute('href', `/course/${defaultProps.courseId}/page-id`);
});
- it('does not render anything when neither legacyLink nor allowedOperations allows configuration or enabling', () => {
- renderComponent({
- id: 'page_4',
- courseId: 'course_4',
- allowedOperations: {},
- }, store);
+ it('renders the settings button with the legacy link when useNewCustomPages is false', () => {
+ useSelector.mockReturnValue({ ...mockWaffleFlags, useNewCustomPages: false });
+
+ renderComponent();
- expect(screen.queryByRole('link')).not.toBeInTheDocument();
- expect(screen.queryByRole('button')).not.toBeInTheDocument();
+ const linkElement = screen.getByRole('link');
+ expect(linkElement).toHaveAttribute('href', defaultProps.legacyLink);
});
});
diff --git a/src/studio-home/card-item/index.tsx b/src/studio-home/card-item/index.tsx
index 4c26ebb8b8..514ffa686f 100644
--- a/src/studio-home/card-item/index.tsx
+++ b/src/studio-home/card-item/index.tsx
@@ -12,6 +12,7 @@ import { useIntl } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
import { Link } from 'react-router-dom';
+import { getWaffleFlags } from '../../data/selectors';
import { COURSE_CREATOR_STATES } from '../../constants';
import { getStudioHomeData } from '../data/selectors';
import messages from '../messages';
@@ -60,7 +61,7 @@ const CardItem: React.FC = ({
courseCreatorStatus,
rerunCreatorStatus,
} = useSelector(getStudioHomeData);
- const waffleFlags = useSelector(state => state.courseDetail.waffleFlags);
+ const waffleFlags = useSelector(getWaffleFlags);
const destinationUrl: string = waffleFlags?.useNewCourseOutlinePage
? path ?? url
diff --git a/src/studio-home/data/api.js b/src/studio-home/data/api.js
index fcc6603979..b70af23ddb 100644
--- a/src/studio-home/data/api.js
+++ b/src/studio-home/data/api.js
@@ -13,7 +13,6 @@ export const getCourseNotificationUrl = (url) => new URL(url, getApiBaseUrl()).h
*/
export async function getStudioHomeData() {
const { data } = await getAuthenticatedHttpClient().get(getStudioHomeApiUrl());
-
return camelCaseObject(data);
}
diff --git a/src/studio-home/hooks.jsx b/src/studio-home/hooks.jsx
index 584d69d842..4b50b02d22 100644
--- a/src/studio-home/hooks.jsx
+++ b/src/studio-home/hooks.jsx
@@ -6,6 +6,7 @@ import { RequestStatus } from '../data/constants';
import { COURSE_CREATOR_STATES } from '../constants';
import { getCourseData, getSavingStatus } from '../generic/data/selectors';
import { fetchStudioHomeData } from './data/thunks';
+import { fetchWaffleFlags } from '../data/thunks';
import {
getLoadingStatuses,
getSavingStatuses,
@@ -36,6 +37,7 @@ const useStudioHome = (isPaginated = false) => {
dispatch(fetchStudioHomeData(location.search ?? ''));
setShowNewCourseContainer(false);
}
+ dispatch(fetchWaffleFlags());
}, [location.search]);
useEffect(() => {
diff --git a/src/utils.js b/src/utils.js
index 088f5bb56d..6cadfd9d9a 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -108,7 +108,7 @@ export const createCorrectInternalRoute = (checkPath) => {
}
if (!checkPath.startsWith(basePath)) {
- return `${checkPath}`;
+ return checkPath;
}
return checkPath;