fix: display programs only if the url is configured (#479)
Removes the link of programs from the Header if the service is not configured.
This commit is contained in:
@@ -9,6 +9,7 @@ const getLearnerHeaderMenu = (
|
||||
courseSearchUrl,
|
||||
authenticatedUser,
|
||||
exploreCoursesClick,
|
||||
programsEnabled = false,
|
||||
) => ({
|
||||
mainMenu: [
|
||||
{
|
||||
@@ -17,11 +18,11 @@ const getLearnerHeaderMenu = (
|
||||
content: formatMessage(messages.course),
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
...(programsEnabled ? [{
|
||||
type: 'item',
|
||||
href: `${urls.programsUrl()}`,
|
||||
content: formatMessage(messages.program),
|
||||
},
|
||||
}] : []),
|
||||
{
|
||||
type: 'item',
|
||||
href: `${urls.baseAppUrl(courseSearchUrl)}`,
|
||||
|
||||
@@ -12,11 +12,6 @@ exports[`LearnerDashboardHeader render 1`] = `
|
||||
"isActive": true,
|
||||
"type": "item",
|
||||
},
|
||||
{
|
||||
"content": "Programs",
|
||||
"href": "http://localhost:18000/dashboard/programs",
|
||||
"type": "item",
|
||||
},
|
||||
{
|
||||
"content": "Discover New",
|
||||
"href": "http://localhost:18000/course-search-url",
|
||||
|
||||
@@ -5,6 +5,7 @@ import track from 'tracking';
|
||||
import { StrictDict } from 'utils';
|
||||
import { linkNames } from 'tracking/constants';
|
||||
|
||||
import { apiHooks } from 'hooks';
|
||||
import getLearnerHeaderMenu from './LearnerDashboardMenu';
|
||||
|
||||
import * as module from './hooks';
|
||||
@@ -30,8 +31,9 @@ export const findCoursesNavDropdownClicked = (href) => track.findCourses.findCou
|
||||
export const useLearnerDashboardHeaderMenu = ({
|
||||
courseSearchUrl, authenticatedUser, exploreCoursesClick,
|
||||
}) => {
|
||||
const { enabled: programsEnabled } = apiHooks.useProgramsConfig();
|
||||
const { formatMessage } = useIntl();
|
||||
return getLearnerHeaderMenu(formatMessage, courseSearchUrl, authenticatedUser, exploreCoursesClick);
|
||||
return getLearnerHeaderMenu(formatMessage, courseSearchUrl, authenticatedUser, exploreCoursesClick, programsEnabled);
|
||||
};
|
||||
|
||||
export const useLearnerDashboardHeaderData = () => {
|
||||
|
||||
@@ -21,6 +21,11 @@ jest.mock('tracking', () => ({
|
||||
findCoursesClicked: jest.fn(),
|
||||
},
|
||||
}));
|
||||
jest.mock('hooks', () => ({
|
||||
apiHooks: {
|
||||
useProgramsConfig: jest.fn(() => ({})),
|
||||
},
|
||||
}));
|
||||
|
||||
const url = 'http://example.com';
|
||||
|
||||
@@ -56,7 +61,7 @@ describe('LearnerDashboardHeader hooks', () => {
|
||||
username: 'test',
|
||||
};
|
||||
const learnerHomeHeaderMenu = useLearnerDashboardHeaderMenu({ courseSearchUrl, authenticatedUser });
|
||||
expect(learnerHomeHeaderMenu.mainMenu.length).toBe(3);
|
||||
expect(learnerHomeHeaderMenu.mainMenu.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { shallow } from '@edx/react-unit-test-utils';
|
||||
import Header from '@edx/frontend-component-header';
|
||||
|
||||
import urls from 'data/services/lms/urls';
|
||||
import { apiHooks } from 'hooks';
|
||||
import LearnerDashboardHeader from '.';
|
||||
import { findCoursesNavClicked } from './hooks';
|
||||
|
||||
@@ -12,6 +13,9 @@ jest.mock('hooks', () => ({
|
||||
courseSearchUrl: '/course-search-url',
|
||||
})),
|
||||
},
|
||||
apiHooks: {
|
||||
useProgramsConfig: jest.fn(() => ({})),
|
||||
},
|
||||
}));
|
||||
jest.mock('./hooks', () => ({
|
||||
...jest.requireActual('./hooks'),
|
||||
@@ -29,7 +33,7 @@ describe('LearnerDashboardHeader', () => {
|
||||
expect(wrapper.instance.findByType('ConfirmEmailBanner')).toHaveLength(1);
|
||||
expect(wrapper.instance.findByType('MasqueradeBar')).toHaveLength(1);
|
||||
expect(wrapper.instance.findByType(Header)).toHaveLength(1);
|
||||
wrapper.instance.findByType(Header)[0].props.mainMenuItems[2].onClick();
|
||||
wrapper.instance.findByType(Header)[0].props.mainMenuItems[1].onClick();
|
||||
expect(findCoursesNavClicked).toHaveBeenCalledWith(urls.baseAppUrl('/course-search-url'));
|
||||
expect(wrapper.instance.findByType(Header)[0].props.secondaryMenuItems.length).toBe(0);
|
||||
});
|
||||
@@ -38,5 +42,11 @@ describe('LearnerDashboardHeader', () => {
|
||||
mergeConfig({ SUPPORT_URL: 'http://localhost:18000/support' });
|
||||
const wrapper = shallow(<LearnerDashboardHeader />);
|
||||
expect(wrapper.instance.findByType(Header)[0].props.secondaryMenuItems.length).toBe(1);
|
||||
expect(wrapper.instance.findByType(Header)[0].props.mainMenuItems.length).toBe(2);
|
||||
});
|
||||
test('should display Programs link if the service is configured in the backend', () => {
|
||||
apiHooks.useProgramsConfig.mockReturnValue({ enabled: true });
|
||||
const wrapper = shallow(<LearnerDashboardHeader />);
|
||||
expect(wrapper.instance.findByType(Header)[0].props.mainMenuItems.length).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,6 +20,8 @@ export const initializeList = ({ user } = {}) => get(
|
||||
stringifyUrl(urls.getInitApiUrl(), { [apiKeys.user]: user }),
|
||||
);
|
||||
|
||||
export const getProgramsConfig = () => get(urls.programsConfigUrl());
|
||||
|
||||
export const updateEntitlementEnrollment = ({ uuid, courseId }) => post(
|
||||
urls.entitlementEnrollment(uuid),
|
||||
{ [apiKeys.courseRunId]: courseId },
|
||||
@@ -73,6 +75,7 @@ export const createCreditRequest = ({ providerId, courseId, username }) => post(
|
||||
|
||||
export default {
|
||||
initializeList,
|
||||
getProgramsConfig,
|
||||
unenrollFromCourse,
|
||||
updateEmailSettings,
|
||||
updateEntitlementEnrollment,
|
||||
|
||||
@@ -22,6 +22,7 @@ export const baseAppUrl = (url) => updateUrl(getBaseUrl(), url);
|
||||
export const learningMfeUrl = (url) => updateUrl(getConfig().LEARNING_BASE_URL, url);
|
||||
|
||||
// static view url
|
||||
const programsConfigUrl = () => baseAppUrl('/config/programs');
|
||||
const programsUrl = () => baseAppUrl('/dashboard/programs');
|
||||
|
||||
export const creditPurchaseUrl = (courseId) => `${getEcommerceUrl()}/credit/checkout/${courseId}/`;
|
||||
@@ -37,6 +38,7 @@ export default StrictDict({
|
||||
event,
|
||||
getInitApiUrl,
|
||||
learningMfeUrl,
|
||||
programsConfigUrl,
|
||||
programsUrl,
|
||||
updateEmailSettings,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import { logError } from '@edx/frontend-platform/logging';
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
|
||||
import { RequestKeys } from 'data/constants/requests';
|
||||
@@ -31,6 +32,25 @@ export const useInitializeApp = () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useProgramsConfig = () => {
|
||||
const [config, setConfig] = React.useState({});
|
||||
|
||||
React.useEffect(() => {
|
||||
const fetchProgramsConfig = async () => {
|
||||
try {
|
||||
const { data } = await api.getProgramsConfig();
|
||||
setConfig(data);
|
||||
} catch (error) {
|
||||
logError(`Error accessing programs configuration ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
fetchProgramsConfig();
|
||||
}, []);
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
export const useNewEntitlementEnrollment = (cardId) => {
|
||||
const { uuid } = reduxHooks.useCardEntitlementData(cardId);
|
||||
const onSuccess = module.useInitializeApp();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { logError } from '@edx/frontend-platform/logging';
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
import { keyStore } from 'utils';
|
||||
import { RequestKeys } from 'data/constants/requests';
|
||||
@@ -10,9 +11,14 @@ import * as apiHooks from './api';
|
||||
|
||||
const reduxKeys = keyStore(reduxHooks);
|
||||
|
||||
jest.mock('@edx/frontend-platform/logging', () => ({
|
||||
logError: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('data/services/lms/utils', () => ({
|
||||
post: jest.fn((...args) => ({ post: args })),
|
||||
}));
|
||||
|
||||
jest.mock('data/services/lms/api', () => ({
|
||||
initializeList: jest.fn(),
|
||||
updateEntitlementEnrollment: jest.fn(),
|
||||
@@ -20,7 +26,9 @@ jest.mock('data/services/lms/api', () => ({
|
||||
deleteEntitlementEnrollment: jest.fn(),
|
||||
updateEmailSettings: jest.fn(),
|
||||
createCreditRequest: jest.fn(),
|
||||
getProgramsConfig: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('data/redux/hooks', () => ({
|
||||
useCardCourseRunData: jest.fn(),
|
||||
useCardCreditData: jest.fn(),
|
||||
@@ -110,6 +118,34 @@ describe('api hooks', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('useProgramsConfig', () => {
|
||||
let mockState;
|
||||
const setState = jest.fn((newState) => { Object.assign(mockState, newState); });
|
||||
beforeEach(() => {
|
||||
mockState = {};
|
||||
React.useState.mockReturnValue([mockState, setState]);
|
||||
});
|
||||
|
||||
it('should return the programs configuration when the API call is successful', async () => {
|
||||
api.getProgramsConfig.mockResolvedValue({ data: { enabled: true } });
|
||||
const config = apiHooks.useProgramsConfig();
|
||||
const [cb] = React.useEffect.mock.calls[0];
|
||||
await cb();
|
||||
expect(setState).toHaveBeenCalled();
|
||||
expect(config).toEqual({ enabled: true });
|
||||
});
|
||||
|
||||
it('should return an empty object if the api call fails', async () => {
|
||||
mockState = {};
|
||||
api.getProgramsConfig.mockRejectedValue(new Error('error test'));
|
||||
const config = apiHooks.useProgramsConfig();
|
||||
const [cb] = React.useEffect.mock.calls[0];
|
||||
await cb();
|
||||
expect(config).toEqual({});
|
||||
expect(logError).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('entitlement enrollment hooks', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(apiHooks, moduleKeys.useInitializeApp).mockReturnValue(initializeApp);
|
||||
|
||||
Reference in New Issue
Block a user