header unit tests
This commit is contained in:
@@ -7,25 +7,29 @@ import { Button } from '@edx/paragon';
|
||||
|
||||
import message from './AnonymousUserMenu.messages';
|
||||
|
||||
function AnonymousUserMenu({ intl }) {
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
className="mr-3"
|
||||
variant="outline-primary"
|
||||
href={`${getConfig().LMS_BASE_URL}/register?next=${encodeURIComponent(global.location.href)}`}
|
||||
>
|
||||
{intl.formatMessage(message.registerSentenceCase)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
href={`${getLoginRedirectUrl(global.location.href)}`}
|
||||
>
|
||||
{intl.formatMessage(message.signInSentenceCase)}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export const getRegisterUrl = () => {
|
||||
const { LMS_BASE_URL } = getConfig();
|
||||
const locationHref = encodeURIComponent(global.location.href);
|
||||
return `${LMS_BASE_URL}/register?next=${locationHref}`;
|
||||
};
|
||||
|
||||
export const AnonymousUserMenu = ({ intl }) => (
|
||||
<div>
|
||||
<Button
|
||||
className="mr-3"
|
||||
variant="outline-primary"
|
||||
href={getRegisterUrl()}
|
||||
>
|
||||
{intl.formatMessage(message.registerSentenceCase)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
href={`${getLoginRedirectUrl(global.location.href)}`}
|
||||
>
|
||||
{intl.formatMessage(message.signInSentenceCase)}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
AnonymousUserMenu.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
close: {
|
||||
id: 'general.altText.close',
|
||||
defaultMessage: 'Close',
|
||||
description: 'Text used as an aria-label to describe closing or dismissing a component',
|
||||
},
|
||||
registerLowercase: {
|
||||
id: 'learning.logistration.register', // ID left for historical purposes
|
||||
defaultMessage: 'register',
|
||||
description: 'Text in a link, prompting the user to create an account. Used in "learning.logistration.alert"',
|
||||
},
|
||||
registerSentenceCase: {
|
||||
id: 'general.register.sentenceCase',
|
||||
defaultMessage: 'Register',
|
||||
description: 'Text in a button, prompting the user to register.',
|
||||
},
|
||||
signInLowercase: {
|
||||
id: 'learning.logistration.login', // ID left for historical purposes
|
||||
defaultMessage: 'sign in',
|
||||
description: 'Text in a link, prompting the user to log in. Used in "learning.logistration.alert"',
|
||||
},
|
||||
signInSentenceCase: {
|
||||
id: 'general.signIn.sentenceCase',
|
||||
defaultMessage: 'Sign in',
|
||||
description: 'Text in a button, prompting the user to log in.',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
27
src/containers/CourseHeader/AnonymousUserMenu.test.jsx
Normal file
27
src/containers/CourseHeader/AnonymousUserMenu.test.jsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { AnonymousUserMenu } from './AnonymousUserMenu';
|
||||
|
||||
jest.mock('@edx/frontend-platform', () => ({
|
||||
getConfig: () => ({
|
||||
LMS_BASE_URL: '<LMS_BASE_URL>',
|
||||
}),
|
||||
}));
|
||||
jest.mock('@edx/frontend-platform/auth', () => ({
|
||||
getLoginRedirectUrl: (url) => `redirect:${url}`,
|
||||
}));
|
||||
jest.mock('@edx/paragon', () => ({
|
||||
Button: () => 'Button',
|
||||
}));
|
||||
|
||||
describe('Header AnonymousUserMenu component', () => {
|
||||
const props = {
|
||||
intl: { formatMessage: (msg) => msg.defaultMessage },
|
||||
};
|
||||
test('snapshot', () => {
|
||||
expect(
|
||||
shallow(<AnonymousUserMenu {...props} />),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,53 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faUserCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Dropdown } from '@edx/paragon';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
function AuthenticatedUserDropdown({ intl, username }) {
|
||||
let dashboardMenuItem = (
|
||||
<Dropdown.Item href={`${getConfig().LMS_BASE_URL}/dashboard`}>
|
||||
{intl.formatMessage(messages.dashboard)}
|
||||
</Dropdown.Item>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<a className="text-gray-700 mr-3" href={`${getConfig().SUPPORT_URL}`}>{intl.formatMessage(messages.help)}</a>
|
||||
<Dropdown className="user-dropdown">
|
||||
<Dropdown.Toggle variant="outline-primary">
|
||||
<FontAwesomeIcon icon={faUserCircle} className="d-md-none" size="lg" />
|
||||
<span data-hj-suppress className="d-none d-md-inline">
|
||||
{username}
|
||||
</span>
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu className="dropdown-menu-right">
|
||||
{dashboardMenuItem}
|
||||
<Dropdown.Item href={`${getConfig().LMS_BASE_URL}/u/${username}`}>
|
||||
{intl.formatMessage(messages.profile)}
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item href={`${getConfig().LMS_BASE_URL}/account/settings`}>
|
||||
{intl.formatMessage(messages.account)}
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item href={getConfig().LOGOUT_URL}>
|
||||
{intl.formatMessage(messages.signOut)}
|
||||
</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
AuthenticatedUserDropdown.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
AuthenticatedUserDropdown.defaultProps = {};
|
||||
|
||||
export default injectIntl(AuthenticatedUserDropdown);
|
||||
@@ -1,53 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faUserCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Dropdown } from '@edx/paragon';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
function AuthenticatedUserDropdown({ intl, username }) {
|
||||
let dashboardMenuItem = (
|
||||
<Dropdown.Item href={`${getConfig().LMS_BASE_URL}/dashboard`}>
|
||||
{intl.formatMessage(messages.dashboard)}
|
||||
</Dropdown.Item>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<a className="text-gray-700 mr-3" href={`${getConfig().SUPPORT_URL}`}>{intl.formatMessage(messages.help)}</a>
|
||||
<Dropdown className="user-dropdown">
|
||||
<Dropdown.Toggle variant="outline-primary">
|
||||
<FontAwesomeIcon icon={faUserCircle} className="d-md-none" size="lg" />
|
||||
<span data-hj-suppress className="d-none d-md-inline">
|
||||
{username}
|
||||
</span>
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu className="dropdown-menu-right">
|
||||
{dashboardMenuItem}
|
||||
<Dropdown.Item href={`${getConfig().LMS_BASE_URL}/u/${username}`}>
|
||||
{intl.formatMessage(messages.profile)}
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item href={`${getConfig().LMS_BASE_URL}/account/settings`}>
|
||||
{intl.formatMessage(messages.account)}
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item href={getConfig().LOGOUT_URL}>
|
||||
{intl.formatMessage(messages.signOut)}
|
||||
</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
AuthenticatedUserDropdown.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
AuthenticatedUserDropdown.defaultProps = {};
|
||||
|
||||
export default injectIntl(AuthenticatedUserDropdown);
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faUserCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import { Dropdown } from '@edx/paragon';
|
||||
|
||||
export const UserAvatar = ({ username }) => (
|
||||
<Dropdown.Toggle variant="outline-primary">
|
||||
<FontAwesomeIcon
|
||||
icon={faUserCircle}
|
||||
className="d-md-none"
|
||||
size="lg"
|
||||
/>
|
||||
<span data-hj-suppress className="d-none d-md-inline">
|
||||
{username}
|
||||
</span>
|
||||
</Dropdown.Toggle>
|
||||
);
|
||||
UserAvatar.propTypes = {
|
||||
username: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
UserAvatar.defaultProps = {};
|
||||
|
||||
export default UserAvatar;
|
||||
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import UserAvatar from './UserAvatar';
|
||||
|
||||
jest.mock('@edx/frontend-platform', () => ({
|
||||
getConfig: () => ({
|
||||
LMS_BASE_URL: '<LMS_BASE_URL>',
|
||||
LOGOUT_URL: '<LOGOUT_URL>',
|
||||
SUPPORT_URL: '<SUPPORT_URL>',
|
||||
}),
|
||||
}));
|
||||
jest.mock('@edx/paragon', () => ({
|
||||
Dropdown: {
|
||||
Toggle: () => 'Dropdown.Toggle',
|
||||
Item: () => 'Dropdown.Item',
|
||||
},
|
||||
}));
|
||||
jest.mock('@fortawesome/react-fontawesome', () => ({
|
||||
FontAwesomeIcon: () => 'FontAwesomeIcon',
|
||||
}));
|
||||
|
||||
describe('Header AuthenticatedUserDropdown UserAvatar component', () => {
|
||||
const props = {
|
||||
username: 'test-username',
|
||||
};
|
||||
test('snapshot', () => {
|
||||
expect(
|
||||
shallow(<UserAvatar {...props} />),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Dropdown } from '@edx/paragon';
|
||||
|
||||
import messages from '../messages';
|
||||
|
||||
export class UserMenu extends React.Component {
|
||||
menuItem(href, message) {
|
||||
return (
|
||||
<Dropdown.Item href={href}>
|
||||
{this.props.intl.formatMessage(message)}
|
||||
</Dropdown.Item>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { username } = this.props;
|
||||
const { LMS_BASE_URL, LOGOUT_URL } = getConfig();
|
||||
return (
|
||||
<Dropdown.Menu className="dropdown-menu-right">
|
||||
{this.menuItem(`${LMS_BASE_URL}/dashboard`, messages.dashboard)}
|
||||
{this.menuItem(`${LMS_BASE_URL}/u/${username}`, messages.profile)}
|
||||
{this.menuItem(`${LMS_BASE_URL}/account/settings`, messages.account)}
|
||||
{this.menuItem(LOGOUT_URL, messages.signOut)}
|
||||
</Dropdown.Menu>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
UserMenu.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
UserMenu.defaultProps = {};
|
||||
|
||||
export default injectIntl(UserMenu);
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { AuthenticatedUserDropdown } from './AuthenticatedUserDropdown';
|
||||
|
||||
jest.mock('@edx/frontend-platform', () => ({
|
||||
getConfig: () => ({
|
||||
LMS_BASE_URL: '<LMS_BASE_URL>',
|
||||
LOGOUT_URL: '<LOGOUT_URL>',
|
||||
SUPPORT_URL: '<SUPPORT_URL>',
|
||||
}),
|
||||
}));
|
||||
jest.mock('@edx/paragon', () => {
|
||||
const Dropdown = () => 'Dropdown';
|
||||
Dropdown.Toggle = () => 'Dropdown.Toggle';
|
||||
Dropdown.Menu = () => 'Dropdown.Menu';
|
||||
Dropdown.Item = () => 'Dropdown.Item';
|
||||
return { Dropdown };
|
||||
});
|
||||
jest.mock('@fortawesome/react-fontawesome', () => ({
|
||||
FontAwesomeIcon: () => 'FontAwesomeIcon',
|
||||
}));
|
||||
jest.mock('@fortawesome/free-solid-svg-icons', () => ({
|
||||
faUserCircle: 'fa-user-circle-icon',
|
||||
}));
|
||||
|
||||
describe('Header AuthenticatedUserDropdown component', () => {
|
||||
const props = {
|
||||
intl: { formatMessage: (msg) => msg.defaultMessage },
|
||||
username: 'test-username',
|
||||
};
|
||||
test('snapshot', () => {
|
||||
expect(
|
||||
shallow(<AuthenticatedUserDropdown {...props} />),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Header AuthenticatedUserDropdown UserAvatar component snapshot 1`] = `
|
||||
<Toggle
|
||||
variant="outline-primary"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
className="d-md-none"
|
||||
icon={
|
||||
Object {
|
||||
"icon": Array [
|
||||
496,
|
||||
512,
|
||||
Array [],
|
||||
"f2bd",
|
||||
"M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 96c48.6 0 88 39.4 88 88s-39.4 88-88 88-88-39.4-88-88 39.4-88 88-88zm0 344c-58.7 0-111.3-26.6-146.5-68.2 18.8-35.4 55.6-59.8 98.5-59.8 2.4 0 4.8.4 7.1 1.1 13 4.2 26.6 6.9 40.9 6.9 14.3 0 28-2.7 40.9-6.9 2.3-.7 4.7-1.1 7.1-1.1 42.9 0 79.7 24.4 98.5 59.8C359.3 421.4 306.7 448 248 448z",
|
||||
],
|
||||
"iconName": "user-circle",
|
||||
"prefix": "fas",
|
||||
}
|
||||
}
|
||||
size="lg"
|
||||
/>
|
||||
<span
|
||||
className="d-none d-md-inline"
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
test-username
|
||||
</span>
|
||||
</Toggle>
|
||||
`;
|
||||
@@ -0,0 +1,20 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Header AuthenticatedUserDropdown component snapshot 1`] = `
|
||||
<Fragment>
|
||||
<a
|
||||
className="text-gray-700 mr-3"
|
||||
href="<SUPPORT_URL>"
|
||||
>
|
||||
Help
|
||||
</a>
|
||||
<Dropdown
|
||||
className="user-dropdown"
|
||||
>
|
||||
<UserAvatar
|
||||
username="test-username"
|
||||
/>
|
||||
<UserMenu />
|
||||
</Dropdown>
|
||||
</Fragment>
|
||||
`;
|
||||
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faUserCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Dropdown } from '@edx/paragon';
|
||||
|
||||
import UserMenu from './UserMenu';
|
||||
import UserAvatar from './UserAvatar';
|
||||
|
||||
import messages from '../messages';
|
||||
|
||||
export const AuthenticatedUserDropdown = ({
|
||||
intl,
|
||||
username,
|
||||
}) => (
|
||||
<>
|
||||
<a className="text-gray-700 mr-3" href={`${getConfig().SUPPORT_URL}`}>
|
||||
{intl.formatMessage(messages.help)}
|
||||
</a>
|
||||
<Dropdown className="user-dropdown">
|
||||
<UserAvatar username={username} />
|
||||
<UserMenu />
|
||||
</Dropdown>
|
||||
</>
|
||||
);
|
||||
|
||||
AuthenticatedUserDropdown.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
AuthenticatedUserDropdown.defaultProps = {};
|
||||
|
||||
export default injectIntl(AuthenticatedUserDropdown);
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { AuthenticatedUserDropdown } from '.';
|
||||
|
||||
jest.mock('@edx/frontend-platform', () => ({
|
||||
getConfig: () => ({
|
||||
SUPPORT_URL: '<SUPPORT_URL>',
|
||||
}),
|
||||
}));
|
||||
jest.mock('@edx/paragon', () => ({
|
||||
Dropdown: () => 'Dropdown',
|
||||
}));
|
||||
jest.mock('./UserAvatar', () => 'UserAvatar');
|
||||
jest.mock('./UserMenu', () => 'UserMenu');
|
||||
|
||||
describe('Header AuthenticatedUserDropdown component', () => {
|
||||
const props = {
|
||||
intl: { formatMessage: (msg) => msg.defaultMessage },
|
||||
username: 'test-username',
|
||||
};
|
||||
test('snapshot', () => {
|
||||
expect(
|
||||
shallow(<AuthenticatedUserDropdown {...props} />),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
32
src/containers/CourseHeader/CourseLabel.jsx
Normal file
32
src/containers/CourseHeader/CourseLabel.jsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export const CourseLabel = ({
|
||||
courseOrg,
|
||||
courseNumber,
|
||||
courseTitle,
|
||||
}) => (
|
||||
<div
|
||||
className="flex-grow-1 course-title-lockup"
|
||||
style={{ lineHeight: 1 }}
|
||||
>
|
||||
<span className="d-block small m-0">
|
||||
{courseOrg} {courseNumber}
|
||||
</span>
|
||||
<span className="d-block m-0 font-weight-bold course-title">
|
||||
{courseTitle}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
CourseLabel.propTypes = {
|
||||
courseOrg: PropTypes.string,
|
||||
courseNumber: PropTypes.string,
|
||||
courseTitle: PropTypes.string,
|
||||
};
|
||||
CourseLabel.defaultProps = {
|
||||
courseOrg: null,
|
||||
courseNumber: null,
|
||||
courseTitle: null,
|
||||
};
|
||||
|
||||
export default CourseLabel;
|
||||
18
src/containers/CourseHeader/CourseLabel.test.jsx
Normal file
18
src/containers/CourseHeader/CourseLabel.test.jsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import CourseLabel from './CourseLabel';
|
||||
|
||||
const courseData = {
|
||||
courseOrg: 'course-org',
|
||||
courseNumber: 'course-number',
|
||||
courseTitle: 'course-title',
|
||||
};
|
||||
|
||||
describe('Header CourseLabel component', () => {
|
||||
test('snapshot', () => {
|
||||
expect(
|
||||
shallow(<CourseLabel {...courseData} />),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,81 +1,47 @@
|
||||
import React, { useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
|
||||
import AnonymousUserMenu from './AnonymousUserMenu';
|
||||
import AuthenticatedUserDropdown from './AuthenticatedUserDropdown';
|
||||
import LinkedLogo from './LinkedLogo';
|
||||
import CourseLabel from './CourseLabel';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
function LinkedLogo({
|
||||
href,
|
||||
src,
|
||||
alt,
|
||||
...attributes
|
||||
}) {
|
||||
return (
|
||||
<a href={href} {...attributes}>
|
||||
<img className="d-block" src={src} alt={alt} />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
LinkedLogo.propTypes = {
|
||||
href: PropTypes.string.isRequired,
|
||||
src: PropTypes.string.isRequired,
|
||||
alt: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
function Header({
|
||||
courseOrg, courseNumber, courseTitle, intl, showUserDropdown,
|
||||
}) {
|
||||
export const Header = ({
|
||||
courseOrg,
|
||||
courseNumber,
|
||||
courseTitle,
|
||||
intl,
|
||||
}) => {
|
||||
const { authenticatedUser } = useContext(AppContext);
|
||||
|
||||
let headerLogo = (
|
||||
<LinkedLogo
|
||||
className="logo"
|
||||
href={`${getConfig().LMS_BASE_URL}/dashboard`}
|
||||
src={getConfig().LOGO_URL}
|
||||
alt={getConfig().SITE_NAME}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<header className="course-header">
|
||||
<a className="sr-only sr-only-focusable" href="#main-content">{intl.formatMessage(messages.skipNavLink)}</a>
|
||||
<a className="sr-only sr-only-focusable" href="#main-content">
|
||||
{intl.formatMessage(messages.skipNavLink)}
|
||||
</a>
|
||||
<div className="container-xl py-2 d-flex align-items-center">
|
||||
{headerLogo}
|
||||
<div className="flex-grow-1 course-title-lockup" style={{ lineHeight: 1 }}>
|
||||
<span className="d-block small m-0">{courseOrg} {courseNumber}</span>
|
||||
<span className="d-block m-0 font-weight-bold course-title">{courseTitle}</span>
|
||||
</div>
|
||||
{showUserDropdown && authenticatedUser && (
|
||||
<AuthenticatedUserDropdown
|
||||
username={authenticatedUser.username}
|
||||
/>
|
||||
)}
|
||||
{showUserDropdown && !authenticatedUser && (
|
||||
<AnonymousUserMenu />
|
||||
)}
|
||||
<LinkedLogo />
|
||||
<CourseLabel {...{ courseOrg, courseNumber, courseTitle }} />
|
||||
{authenticatedUser
|
||||
? (<AuthenticatedUserDropdown username={authenticatedUser.username} />)
|
||||
: (<AnonymousUserMenu />)}
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
Header.propTypes = {
|
||||
courseOrg: PropTypes.string,
|
||||
courseNumber: PropTypes.string,
|
||||
courseTitle: PropTypes.string,
|
||||
intl: intlShape.isRequired,
|
||||
showUserDropdown: PropTypes.bool,
|
||||
};
|
||||
|
||||
Header.defaultProps = {
|
||||
courseOrg: null,
|
||||
courseNumber: null,
|
||||
courseTitle: null,
|
||||
showUserDropdown: true,
|
||||
};
|
||||
|
||||
export default injectIntl(Header);
|
||||
|
||||
@@ -1,29 +1,38 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
authenticatedUser, initializeMockApp, render, screen,
|
||||
} from '../setupTest';
|
||||
import { Header } from './index';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
describe('Header', () => {
|
||||
beforeAll(async () => {
|
||||
// We need to mock AuthService to implicitly use `getAuthenticatedUser` within `AppContext.Provider`.
|
||||
await initializeMockApp();
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
import { Header } from './Header';
|
||||
|
||||
jest.mock('./AnonymousUserMenu', () => 'AnonymousUserMenu');
|
||||
jest.mock('./AuthenticatedUserDropdown', () => 'AuthenticatedUserDropdown');
|
||||
jest.mock('./LinkedLogo', () => 'LinkedLogo');
|
||||
jest.mock('./CourseLabel', () => 'CourseLabel');
|
||||
|
||||
jest.mock('@edx/frontend-platform/react', () => ({
|
||||
AppContext: { authenticatedUser: null },
|
||||
}));
|
||||
jest.mock('react', () => ({
|
||||
...jest.requireActual('react'),
|
||||
useContext: (context) => context,
|
||||
}));
|
||||
|
||||
const courseData = {
|
||||
courseOrg: 'course-org',
|
||||
courseNumber: 'course-number',
|
||||
courseTitle: 'course-title',
|
||||
};
|
||||
|
||||
describe('Header component', () => {
|
||||
const props = {
|
||||
...courseData,
|
||||
intl: { formatMessage: (msg) => msg.defaultMessage },
|
||||
};
|
||||
test('snapshot', () => {
|
||||
expect(shallow(<Header {...props} />)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('displays user button', () => {
|
||||
render(<Header />);
|
||||
expect(screen.getByRole('button')).toHaveTextContent(authenticatedUser.username);
|
||||
});
|
||||
|
||||
it('displays course data', () => {
|
||||
const courseData = {
|
||||
courseOrg: 'course-org',
|
||||
courseNumber: 'course-number',
|
||||
courseTitle: 'course-title',
|
||||
};
|
||||
render(<Header {...courseData} />);
|
||||
|
||||
expect(screen.getByText(`${courseData.courseOrg} ${courseData.courseNumber}`)).toBeInTheDocument();
|
||||
expect(screen.getByText(courseData.courseTitle)).toBeInTheDocument();
|
||||
test('snapshot with authenticatedUser', () => {
|
||||
AppContext.authenticatedUser = { username: 'test' };
|
||||
expect(shallow(<Header {...props} />)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
17
src/containers/CourseHeader/LinkedLogo.jsx
Normal file
17
src/containers/CourseHeader/LinkedLogo.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
|
||||
const LinkedLogo = () => (
|
||||
<a
|
||||
className="logo"
|
||||
href={`${getConfig().LMS_BASE_URL}/dashboard`}
|
||||
>
|
||||
<img
|
||||
className="d-block"
|
||||
src={getConfig().LOGO_URL}
|
||||
alt={getConfig().SITE_NAME}
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
|
||||
export default LinkedLogo;
|
||||
20
src/containers/CourseHeader/LinkedLogo.test.jsx
Normal file
20
src/containers/CourseHeader/LinkedLogo.test.jsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import LinkedLogo from './LinkedLogo';
|
||||
|
||||
jest.mock('@edx/frontend-platform', () => ({
|
||||
getConfig: () => ({
|
||||
LMS_BASE_URL: '<getConfig().LMS_BASE_URL>',
|
||||
LOGO_URL: '<getConfig().LOGO_URL>',
|
||||
SITE_NAME: '<getConfig().SITE_NAME>',
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('Header CourseLabel component', () => {
|
||||
test('snapshot', () => {
|
||||
expect(
|
||||
shallow(<LinkedLogo />),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Header AnonymousUserMenu component snapshot 1`] = `
|
||||
<div>
|
||||
<Button
|
||||
className="mr-3"
|
||||
href="<LMS_BASE_URL>/register?next=http%3A%2F%2Flocalhost%2F"
|
||||
variant="outline-primary"
|
||||
>
|
||||
Register
|
||||
</Button>
|
||||
<Button
|
||||
href="redirect:http://localhost/"
|
||||
variant="primary"
|
||||
>
|
||||
Sign in
|
||||
</Button>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,25 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Header CourseLabel component snapshot 1`] = `
|
||||
<div
|
||||
className="flex-grow-1 course-title-lockup"
|
||||
style={
|
||||
Object {
|
||||
"lineHeight": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<span
|
||||
className="d-block small m-0"
|
||||
>
|
||||
course-org
|
||||
|
||||
course-number
|
||||
</span>
|
||||
<span
|
||||
className="d-block m-0 font-weight-bold course-title"
|
||||
>
|
||||
course-title
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,51 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Header component snapshot 1`] = `
|
||||
<header
|
||||
className="course-header"
|
||||
>
|
||||
<a
|
||||
className="sr-only sr-only-focusable"
|
||||
href="#main-content"
|
||||
>
|
||||
Skip to main content.
|
||||
</a>
|
||||
<div
|
||||
className="container-xl py-2 d-flex align-items-center"
|
||||
>
|
||||
<LinkedLogo />
|
||||
<CourseLabel
|
||||
courseNumber="course-number"
|
||||
courseOrg="course-org"
|
||||
courseTitle="course-title"
|
||||
/>
|
||||
<AnonymousUserMenu />
|
||||
</div>
|
||||
</header>
|
||||
`;
|
||||
|
||||
exports[`Header component snapshot with authenticatedUser 1`] = `
|
||||
<header
|
||||
className="course-header"
|
||||
>
|
||||
<a
|
||||
className="sr-only sr-only-focusable"
|
||||
href="#main-content"
|
||||
>
|
||||
Skip to main content.
|
||||
</a>
|
||||
<div
|
||||
className="container-xl py-2 d-flex align-items-center"
|
||||
>
|
||||
<LinkedLogo />
|
||||
<CourseLabel
|
||||
courseNumber="course-number"
|
||||
courseOrg="course-org"
|
||||
courseTitle="course-title"
|
||||
/>
|
||||
<AuthenticatedUserDropdown
|
||||
username="test"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
`;
|
||||
@@ -0,0 +1,14 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Header CourseLabel component snapshot 1`] = `
|
||||
<a
|
||||
className="logo"
|
||||
href="<getConfig().LMS_BASE_URL>/dashboard"
|
||||
>
|
||||
<img
|
||||
alt="<getConfig().SITE_NAME>"
|
||||
className="d-block"
|
||||
src="<getConfig().LOGO_URL>"
|
||||
/>
|
||||
</a>
|
||||
`;
|
||||
@@ -1,6 +1,11 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
courseMaterial: {
|
||||
id: 'learn.navigation.course.tabs.label',
|
||||
defaultMessage: 'Course Material',
|
||||
description: 'The accessible label for course tabs navigation',
|
||||
},
|
||||
dashboard: {
|
||||
id: 'header.menu.dashboard.label',
|
||||
defaultMessage: 'Dashboard',
|
||||
@@ -21,6 +26,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Account',
|
||||
description: 'The text for the user menu Account navigation link.',
|
||||
},
|
||||
orderHistory: {
|
||||
id: 'header.menu.orderHistory.label',
|
||||
defaultMessage: 'Order History',
|
||||
description: 'The text for the user menu Order History navigation link.',
|
||||
},
|
||||
skipNavLink: {
|
||||
id: 'header.navigation.skipNavLink',
|
||||
defaultMessage: 'Skip to main content.',
|
||||
|
||||
Reference in New Issue
Block a user