Skip to content

Commit

Permalink
fix: disable invalid link Video Uploads for Palm (openedx#513)
Browse files Browse the repository at this point in the history
  • Loading branch information
DmytroAlipov authored and sambapete committed Oct 11, 2023
1 parent 3fe3534 commit 3832999
Show file tree
Hide file tree
Showing 2 changed files with 345 additions and 0 deletions.
194 changes: 194 additions & 0 deletions src/studio-header/Header.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/* eslint-disable jsx-a11y/control-has-associated-label */
/* eslint-disable jsx-a11y/anchor-has-content */
import PropTypes from 'prop-types';
import React, { useContext } from 'react';
import Responsive from 'react-responsive';
import { AppContext } from '@edx/frontend-platform/react';
import { ensureConfig } from '@edx/frontend-platform';
import { OverlayTrigger, Tooltip } from '@edx/paragon';
import {
injectIntl,
intlShape,
} from '@edx/frontend-platform/i18n';

import DesktopHeader from './DesktopHeader';
import MobileHeader from './MobileHeader';
import messages from './Header.messages';

ensureConfig([
'STUDIO_BASE_URL',
'LOGOUT_URL',
'LOGIN_URL',
'LOGO_URL',
], 'Header component');

const Header = ({
courseId, courseNumber, courseOrg, courseTitle, intl,
}) => {
const { authenticatedUser, config } = useContext(AppContext);

const mainMenu = [
{
type: 'submenu',
content: intl.formatMessage(messages['header.links.content']),
submenuContent: (
<>
<div className="mb-1 small">
<a rel="noopener" href={`${config.STUDIO_BASE_URL}/course/${courseId}`}>{intl.formatMessage(messages['header.links.outline'])}</a>
</div>
<div className="mb-1 small">
<a rel="noopener" href={`${config.STUDIO_BASE_URL}/course_info/${courseId}`}>{intl.formatMessage(messages['header.links.updates'])}</a>
</div>
<div className="mb-1 small">
<a rel="noopener" href={`${config.STUDIO_BASE_URL}/tabs/${courseId}`}>{intl.formatMessage(messages['header.links.pages'])}</a>
</div>
<div className="mb-1 small">
<a rel="noopener" href={`${config.STUDIO_BASE_URL}/assets/${courseId}`}>{intl.formatMessage(messages['header.links.filesAndUploads'])}</a>
</div>
<div className="mb-1 small">
<a rel="noopener" href={`${config.STUDIO_BASE_URL}/textbooks/${courseId}`}>{intl.formatMessage(messages['header.links.textbooks'])}</a>
</div>
{process.env.ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN === 'true' && (
<div className="mb-1 small">
<a rel="noopener" href={`${config.STUDIO_BASE_URL}/videos/${courseId}`}>{intl.formatMessage(messages['header.links.videoUploads'])}</a>
</div>
)}
</>
),
},
{
type: 'submenu',
content: intl.formatMessage(messages['header.links.settings']),
submenuContent: (
<>
<div className="mb-1 small">
<a rel="noopener" href={`${config.STUDIO_BASE_URL}/settings/details/${courseId}`}>{intl.formatMessage(messages['header.links.scheduleAndDetails'])}</a>
</div>
<div className="mb-1 small">
<a rel="noopener" href={`${config.STUDIO_BASE_URL}/settings/grading/${courseId}`}>{intl.formatMessage(messages['header.links.grading'])}</a>
</div>
<div className="mb-1 small">
<a rel="noopener" href={`${config.STUDIO_BASE_URL}/course_team/${courseId}`}>{intl.formatMessage(messages['header.links.courseTeam'])}</a>
</div>
<div className="mb-1 small">
<a rel="noopener" href={`${config.STUDIO_BASE_URL}/group_configurations/${courseId}`}>{intl.formatMessage(messages['header.links.groupConfigurations'])}</a>
</div>
<div className="mb-1 small">
<a rel="noopener" href={`${config.STUDIO_BASE_URL}/settings/advanced/${courseId}`}>{intl.formatMessage(messages['header.links.advancedSettings'])}</a>
</div>
<div className="mb-1 small">
<a rel="noopener" href={`${config.STUDIO_BASE_URL}/certificates/${courseId}`}>{intl.formatMessage(messages['header.links.certificates'])}</a>
</div>
</>
),
},
{
type: 'submenu',
content: intl.formatMessage(messages['header.links.tools']),
submenuContent: (
<>
<div className="mb-1 small">
<a rel="noopener" href={`${config.STUDIO_BASE_URL}/import/${courseId}`}>{intl.formatMessage(messages['header.links.import'])}</a>
</div>
<div className="mb-1 small">
<a rel="noopener" href={`${config.STUDIO_BASE_URL}/export/${courseId}`}>{intl.formatMessage(messages['header.links.export'])}</a>
</div>
<div className="mb-1 small">
<a rel="noopener" href={`${config.STUDIO_BASE_URL}/checklists/${courseId}`}>{intl.formatMessage(messages['header.links.checklists'])}</a>
</div>
</>
),
},
];

const studioHomeItem = {
type: 'item',
href: config.STUDIO_BASE_URL,
content: intl.formatMessage(messages['header.user.menu.studio']),
};

const logoutItem = {
type: 'item',
href: config.LOGOUT_URL,
content: intl.formatMessage(messages['header.user.menu.logout']),
};

let userMenu = [];

if (authenticatedUser !== null) {
if (authenticatedUser.administrator) {
userMenu = [
studioHomeItem,
{
type: 'item',
href: `${config.STUDIO_BASE_URL}/maintenance`,
content: intl.formatMessage(messages['header.user.menu.maintenance']),
},
logoutItem,
];
} else {
userMenu = [
studioHomeItem,
logoutItem,
];
}
}

const courseLockUp = (
<OverlayTrigger
placement="bottom"
overlay={(
<Tooltip>
{courseTitle}
</Tooltip>
)}
>
<a
className="course-title-lockup w-25"
href={`${config.STUDIO_BASE_URL}/course/${courseId}`}
aria-label={intl.formatMessage(messages['header.label.courseOutline'])}
>
<span className="d-block small m-0" data-testid="course-org-number">{courseOrg} {courseNumber}</span>
<span className="d-block m-0 font-weight-bold" data-testid="course-title">{courseTitle}</span>
</a>
</OverlayTrigger>
);

const props = {
logo: config.LOGO_URL,
logoAltText: 'Studio edX',
siteName: 'edX',
logoDestination: config.STUDIO_BASE_URL,
courseLockUp,
courseId,
username: authenticatedUser !== null ? authenticatedUser.username : null,
avatar: authenticatedUser !== null ? authenticatedUser.avatar : null,
mainMenu,
userMenu,
};
return (
<>
<Responsive maxWidth={768}>
<MobileHeader {...props} />
</Responsive>
<Responsive minWidth={769}>
<DesktopHeader {...props} />
</Responsive>
</>
);
};

Header.propTypes = {
courseId: PropTypes.string.isRequired,
courseNumber: PropTypes.string,
courseOrg: PropTypes.string,
courseTitle: PropTypes.string.isRequired,
intl: intlShape.isRequired,
};

Header.defaultProps = {
courseNumber: null,
courseOrg: null,
};

export default injectIntl(Header);
151 changes: 151 additions & 0 deletions src/studio-header/Header.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/* eslint-disable react/jsx-no-constructed-context-values */
// This file was copied from edx/frontend-component-header-edx.
import React from 'react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { AppContext } from '@edx/frontend-platform/react';
import { Context as ResponsiveContext } from 'react-responsive';
import {
cleanup,
fireEvent,
render,
screen,
} from '@testing-library/react';

import Header from './Header';

describe('<Header />', () => {
function createComponent(screenWidth, component) {
return (
<ResponsiveContext.Provider value={{ width: screenWidth }}>
<IntlProvider locale="en" messages={{}}>
<AppContext.Provider value={{
authenticatedUser: {
userId: 'abc123',
username: 'edX',
roles: [],
administrator: false,
},
config: {
STUDIO_BASE_URL: process.env.STUDIO_BASE_URL,
LMS_BASE_URL: process.env.LMS_BASE_URL,
LOGIN_URL: process.env.LOGIN_URL,
LOGOUT_URL: process.env.LOGOUT_URL,
},
}}
>
{component}
</AppContext.Provider>
</IntlProvider>
</ResponsiveContext.Provider>
);
}

it('renders desktop header correctly with API call', async () => {
const component = createComponent(
1280, (
<Header
courseId="course-v1:edX+DemoX+Demo_Course"
courseNumber="DemoX"
courseOrg="edX"
courseTitle="Demonstration Course"
/>
),
);

render(component);
expect(screen.getByTestId('course-org-number').textContent).toEqual(expect.stringContaining('edX DemoX'));
expect(screen.getByTestId('course-title').textContent).toEqual(expect.stringContaining('Demonstration Course'));
});

it('renders mobile header correctly with API call', async () => {
const component = createComponent(
500, (
<Header
courseId="course-v1:edX+DemoX+Demo_Course"
courseNumber="DemoX"
courseOrg="edX"
courseTitle="Demonstration Course"
/>
),
);

render(component);
expect(screen.getByTestId('edx-header-logo'));
});

it('renders desktop header correctly with bad API call', async () => {
const component = createComponent(
1280, (
<Header
courseId="course-v1:edX+DemoX+Demo_Course"
courseNumber={null}
courseOrg={null}
courseTitle="course-v1:edX+DemoX+Demo_Course"
/>
),
);

render(component);
expect(screen.getByTestId('course-title').textContent).toEqual(expect.stringContaining('course-v1:edX+DemoX+Demo_Course'));
});

it('renders mobile header correctly with bad API call', async () => {
const component = createComponent(
500, (
<Header
courseId="course-v1:edX+DemoX+Demo_Course"
courseNumber={null}
courseOrg={null}
courseTitle="course-v1:edX+DemoX+Demo_Course"
/>
),
);

render(component);
expect(screen.getByTestId('edx-header-logo'));
});

it('renders Video Uploads link', () => {
process.env.ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN = 'true';

const component = createComponent(
1280, (
<Header
courseId="course-v1:edX+DemoX+Demo_Course"
courseNumber="DemoX"
courseOrg="edX"
courseTitle="Demonstration Course"
/>
),
);

render(component);
fireEvent.click(screen.getByText('Content'));

expect(screen.getByText('Video Uploads')).toBeInTheDocument();
});

it('does not render Video Uploads link', () => {
process.env.ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN = 'false';

const component = createComponent(
1280, (
<Header
courseId="course-v1:edX+DemoX+Demo_Course"
courseNumber="DemoX"
courseOrg="edX"
courseTitle="Demonstration Course"
/>
),
);

render(component);
fireEvent.click(screen.getByText('Content'));

expect(screen.queryByText('Video Uploads')).toBeNull();
});

afterEach(() => {
cleanup();
});
});

0 comments on commit 3832999

Please sign in to comment.