header component (#101)

* chore: use LearningHeader instead course header

* chore: remove course header debris
This commit is contained in:
leangseu-edx
2022-04-20 13:13:03 -04:00
committed by GitHub
parent 91dd10917f
commit 7e9eab24b0
31 changed files with 84 additions and 709 deletions

62
package-lock.json generated
View File

@@ -11,6 +11,7 @@
"dependencies": {
"@edx/brand": "npm:@edx/brand-edx.org@^2.0.3",
"@edx/frontend-component-footer": "10.1.6",
"@edx/frontend-component-header": "^2.4.6",
"@edx/frontend-platform": "^1.15.6",
"@edx/paragon": "16.14.4",
"@fortawesome/fontawesome-svg-core": "^1.2.36",
@@ -3521,6 +3522,28 @@
"react": ">=16.x"
}
},
"node_modules/@edx/frontend-component-header": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/@edx/frontend-component-header/-/frontend-component-header-2.4.6.tgz",
"integrity": "sha512-bwEP3B37N4lIDPM4cz/Dg1egQHWf14qj+3VU44BOf5DC/bNhASKeeEJEvDn6XTZdFCpGioHRBtqlEJQJsp0WqA==",
"dependencies": {
"@fortawesome/fontawesome-svg-core": "1.2.36",
"@fortawesome/free-brands-svg-icons": "5.15.4",
"@fortawesome/free-regular-svg-icons": "5.15.4",
"@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/react-fontawesome": "^0.1.14",
"babel-polyfill": "6.26.0",
"react-responsive": "8.2.0",
"react-transition-group": "4.4.2"
},
"peerDependencies": {
"@edx/frontend-platform": "^1.8.0",
"@edx/paragon": ">= 7.0.0 < 20.0.0",
"prop-types": "^15.5.10",
"react": "^16.9.0",
"react-dom": "^16.9.0"
}
},
"node_modules/@edx/frontend-platform": {
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-1.15.6.tgz",
@@ -8514,7 +8537,6 @@
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz",
"integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=",
"dev": true,
"dependencies": {
"babel-runtime": "^6.26.0",
"core-js": "^2.5.0",
@@ -8526,14 +8548,12 @@
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==",
"deprecated": "core-js@<3.4 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.",
"dev": true,
"hasInstallScript": true
},
"node_modules/babel-polyfill/node_modules/regenerator-runtime": {
"version": "0.10.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
"integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=",
"dev": true
"integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg="
},
"node_modules/babel-preset-current-node-syntax": {
"version": "1.0.1",
@@ -8578,7 +8598,6 @@
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
"integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
"dev": true,
"dependencies": {
"core-js": "^2.4.0",
"regenerator-runtime": "^0.11.0"
@@ -8589,14 +8608,12 @@
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==",
"deprecated": "core-js@<3.4 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.",
"dev": true,
"hasInstallScript": true
},
"node_modules/babel-runtime/node_modules/regenerator-runtime": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==",
"dev": true
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
},
"node_modules/balanced-match": {
"version": "1.0.2",
@@ -36088,6 +36105,21 @@
}
}
},
"@edx/frontend-component-header": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/@edx/frontend-component-header/-/frontend-component-header-2.4.6.tgz",
"integrity": "sha512-bwEP3B37N4lIDPM4cz/Dg1egQHWf14qj+3VU44BOf5DC/bNhASKeeEJEvDn6XTZdFCpGioHRBtqlEJQJsp0WqA==",
"requires": {
"@fortawesome/fontawesome-svg-core": "1.2.36",
"@fortawesome/free-brands-svg-icons": "5.15.4",
"@fortawesome/free-regular-svg-icons": "5.15.4",
"@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/react-fontawesome": "^0.1.14",
"babel-polyfill": "6.26.0",
"react-responsive": "8.2.0",
"react-transition-group": "4.4.2"
}
},
"@edx/frontend-platform": {
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-1.15.6.tgz",
@@ -39986,7 +40018,6 @@
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz",
"integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=",
"dev": true,
"requires": {
"babel-runtime": "^6.26.0",
"core-js": "^2.5.0",
@@ -39996,14 +40027,12 @@
"core-js": {
"version": "2.6.12",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==",
"dev": true
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ=="
},
"regenerator-runtime": {
"version": "0.10.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
"integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=",
"dev": true
"integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg="
}
}
},
@@ -40041,7 +40070,6 @@
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
"integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
"dev": true,
"requires": {
"core-js": "^2.4.0",
"regenerator-runtime": "^0.11.0"
@@ -40050,14 +40078,12 @@
"core-js": {
"version": "2.6.12",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==",
"dev": true
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ=="
},
"regenerator-runtime": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==",
"dev": true
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
}
}
},

View File

@@ -28,6 +28,7 @@
"dependencies": {
"@edx/brand": "npm:@edx/brand-edx.org@^2.0.3",
"@edx/frontend-component-footer": "10.1.6",
"@edx/frontend-component-header": "^2.4.6",
"@edx/frontend-platform": "^1.15.6",
"@edx/paragon": "16.14.4",
"@fortawesome/fontawesome-svg-core": "^1.2.36",

View File

@@ -4,11 +4,11 @@ import { connect } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom';
import Footer from '@edx/frontend-component-footer';
import { LearningHeader as Header } from '@edx/frontend-component-header';
import { selectors } from 'data/redux';
import DemoWarning from 'containers/DemoWarning';
import CourseHeader from 'containers/CourseHeader';
import ListView from 'containers/ListView';
import './App.scss';
@@ -16,7 +16,7 @@ import './App.scss';
export const App = ({ courseMetadata, isEnabled }) => (
<Router>
<div>
<CourseHeader
<Header
courseTitle={courseMetadata.title}
courseNumber={courseMetadata.number}
courseOrg={courseMetadata.org}

View File

@@ -42,32 +42,6 @@ $input-focus-box-shadow: $input-box-shadow; // hack to get upgrade to paragon 4.
}
}
.course-header {
min-width: 0;
border-bottom: 1px solid black;
.course-title-lockup {
min-width: 0;
span {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding-bottom: 0.1rem;
}
}
.user-dropdown {
.btn {
height: 3rem;
@media (max-width: -1 + map-get($grid-breakpoints, "sm")) {
padding: 0 0.5rem;
}
}
}
}
#paragon-portal-root {
.pgn__modal-layer {
.pgn__modal-close-container {

View File

@@ -2,6 +2,7 @@ import React from 'react';
import { shallow } from 'enzyme';
import Footer from '@edx/frontend-component-footer';
import { LearningHeader as Header } from '@edx/frontend-component-header';
import ListView from 'containers/ListView';
@@ -16,11 +17,13 @@ jest.mock('data/redux', () => ({
},
}));
jest.mock('@edx/frontend-component-header', () => ({
LearningHeader: 'Header',
}));
jest.mock('@edx/frontend-component-footer', () => 'Footer');
jest.mock('containers/DemoWarning', () => 'DemoWarning');
jest.mock('containers/ListView', () => 'ListView');
jest.mock('containers/CourseHeader', () => 'CourseHeader');
const logo = 'fakeLogo.png';
let el;
@@ -57,5 +60,16 @@ describe('App router component', () => {
test('Footer logo drawn from env variable', () => {
expect(router.find(Footer).props().logo).toEqual(logo);
});
test('Header to use courseMetadata props', () => {
const {
courseTitle,
courseNumber,
courseOrg,
} = router.find(Header).props();
expect(courseTitle).toEqual(props.courseMetadata.title);
expect(courseNumber).toEqual(props.courseMetadata.number);
expect(courseOrg).toEqual(props.courseMetadata.org);
});
});
});

View File

@@ -3,7 +3,7 @@
exports[`App router component snapshot: disabled (show demo warning) 1`] = `
<BrowserRouter>
<div>
<CourseHeader
<Header
courseNumber="course-number"
courseOrg="course-org"
courseTitle="course-title"
@@ -22,7 +22,7 @@ exports[`App router component snapshot: disabled (show demo warning) 1`] = `
exports[`App router component snapshot: enabled 1`] = `
<BrowserRouter>
<div>
<CourseHeader
<Header
courseNumber="course-number"
courseOrg="course-org"
courseTitle="course-title"

View File

@@ -1,38 +0,0 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { getLoginRedirectUrl } from '@edx/frontend-platform/auth';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Button } from '@edx/paragon';
import message from './messages';
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,
};
export default injectIntl(AnonymousUserMenu);

View File

@@ -1,24 +0,0 @@
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}`,
}));
describe('Header AnonymousUserMenu component', () => {
const props = {
intl: { formatMessage: (msg) => msg.defaultMessage },
};
test('snapshot', () => {
expect(
shallow(<AnonymousUserMenu {...props} />),
).toMatchSnapshot();
});
});

View File

@@ -1,27 +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 { 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;

View File

@@ -1,23 +0,0 @@
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>',
}),
}));
describe('Header AuthenticatedUserDropdown UserAvatar component', () => {
const props = {
username: 'test-username',
};
test('snapshot', () => {
expect(
shallow(<UserAvatar {...props} />),
).toMatchSnapshot();
});
});

View File

@@ -1,40 +0,0 @@
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);

View File

@@ -1,24 +0,0 @@
import React from 'react';
import { shallow } from 'enzyme';
import { UserMenu } from './UserMenu';
jest.mock('@edx/frontend-platform', () => ({
getConfig: () => ({
LMS_BASE_URL: '<LMS_BASE_URL>',
LOGOUT_URL: '<LOGOUT_URL>',
SUPPORT_URL: '<SUPPORT_URL>',
}),
}));
describe('Header AuthenticatedUserDropdown UserMenu component', () => {
const props = {
intl: { formatMessage: (msg) => msg.defaultMessage },
username: 'test-username',
};
test('snapshot', () => {
expect(
shallow(<UserMenu {...props} />),
).toMatchSnapshot();
});
});

View File

@@ -1,31 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Header AuthenticatedUserDropdown UserAvatar component snapshot 1`] = `
<Dropdown.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>
</Dropdown.Toggle>
`;

View File

@@ -1,28 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Header AuthenticatedUserDropdown UserMenu component snapshot 1`] = `
<Dropdown.Menu
className="dropdown-menu-right"
>
<Dropdown.Item
href="<LMS_BASE_URL>/dashboard"
>
Dashboard
</Dropdown.Item>
<Dropdown.Item
href="<LMS_BASE_URL>/u/test-username"
>
Profile
</Dropdown.Item>
<Dropdown.Item
href="<LMS_BASE_URL>/account/settings"
>
Account
</Dropdown.Item>
<Dropdown.Item
href="<LOGOUT_URL>"
>
Sign Out
</Dropdown.Item>
</Dropdown.Menu>
`;

View File

@@ -1,22 +0,0 @@
// 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
username="test-username"
/>
</Dropdown>
</Fragment>
`;

View File

@@ -1,35 +0,0 @@
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 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 username={username} />
</Dropdown>
</>
);
AuthenticatedUserDropdown.propTypes = {
intl: intlShape.isRequired,
username: PropTypes.string.isRequired,
};
AuthenticatedUserDropdown.defaultProps = {};
export default injectIntl(AuthenticatedUserDropdown);

View File

@@ -1,24 +0,0 @@
import React from 'react';
import { shallow } from 'enzyme';
import { AuthenticatedUserDropdown } from '.';
jest.mock('@edx/frontend-platform', () => ({
getConfig: () => ({
SUPPORT_URL: '<SUPPORT_URL>',
}),
}));
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();
});
});

View File

@@ -1,32 +0,0 @@
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;

View File

@@ -1,18 +0,0 @@
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();
});
});

View File

@@ -1,17 +0,0 @@
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;

View File

@@ -1,20 +0,0 @@
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();
});
});

View File

@@ -1,19 +0,0 @@
// 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>
`;

View File

@@ -1,25 +0,0 @@
// 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>
`;

View File

@@ -1,14 +0,0 @@
// 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>
`;

View File

@@ -1,51 +0,0 @@
// 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>
`;

View File

@@ -1,47 +0,0 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
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';
export const Header = ({
courseOrg,
courseNumber,
courseTitle,
intl,
}) => {
const { authenticatedUser } = useContext(AppContext);
return (
<header className="course-header">
<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">
<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,
};
Header.defaultProps = {
courseOrg: null,
courseNumber: null,
courseTitle: null,
};
export default injectIntl(Header);

View File

@@ -1,38 +0,0 @@
import React from 'react';
import { shallow } from 'enzyme';
import { AppContext } from '@edx/frontend-platform/react';
import { Header } from '.';
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();
});
test('snapshot with authenticatedUser', () => {
AppContext.authenticatedUser = { username: 'test' };
expect(shallow(<Header {...props} />)).toMatchSnapshot();
});
});

View File

@@ -1,56 +0,0 @@
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',
description: 'The text for the user menu Dashboard navigation link.',
},
help: {
id: 'header.help.label',
defaultMessage: 'Help',
description: 'The text for the link to the Help Center',
},
profile: {
id: 'header.menu.profile.label',
defaultMessage: 'Profile',
description: 'The text for the user menu Profile navigation link.',
},
account: {
id: 'header.menu.account.label',
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.',
description: 'A link used by screen readers to allow users to skip to the main content of the page.',
},
signOut: {
id: 'header.menu.signOut.label',
defaultMessage: 'Sign Out',
description: 'The label for the user menu Sign Out action.',
},
registerSentenceCase: {
id: 'header.register.sentenceCase',
defaultMessage: 'Register',
description: 'Text in a button, prompting the user to register.',
},
signInSentenceCase: {
id: 'header.signIn.sentenceCase',
defaultMessage: 'Sign in',
description: 'Text in a button, prompting the user to log in.',
},
});
export default messages;

View File

@@ -11,8 +11,12 @@ import {
APP_INIT_ERROR,
initialize,
subscribe,
mergeConfig,
} from '@edx/frontend-platform';
import { messages as footerMessages } from '@edx/frontend-component-footer';
import { messages as headerMesssages } from '@edx/frontend-component-header';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import messages from './i18n';
@@ -38,8 +42,16 @@ subscribe(APP_INIT_ERROR, (error) => {
});
initialize({
handlers: {
config: () => {
mergeConfig({
SUPPORT_URL: process.env.SUPPORT_URL || null,
}, 'OraGradingAppConfig');
},
},
messages: [
messages,
headerMesssages,
footerMessages,
],
requireAuthenticatedUser: true,

View File

@@ -6,7 +6,9 @@ import {
initialize,
subscribe,
} from '@edx/frontend-platform';
import { messages as footerMessages } from '@edx/frontend-component-footer';
import { messages as headerMesssages } from '@edx/frontend-component-header';
import appMessages from './i18n';
import App from './App';
@@ -36,17 +38,18 @@ describe('app registry', () => {
afterAll(() => {
window.document.getElementById = getElement;
});
test('subscribe is called for APP_READY, linking App to root element', () => {
const callArgs = subscribe.mock.calls[0];
const callArgs = subscribe.mock.calls[1];
expect(callArgs[0]).toEqual(APP_READY);
expect(callArgs[1]()).toEqual(
ReactDOM.render(<App />, document.getElementById('root')),
);
});
test('initialize is called with footerMessages and requireAuthenticatedUser', () => {
expect(initialize).toHaveBeenCalledWith({
messages: [appMessages, footerMessages],
requireAuthenticatedUser: true,
});
expect(initialize).toHaveBeenCalledTimes(1);
const initializeArg = initialize.mock.calls[0][0];
expect(initializeArg.messages).toEqual([appMessages, headerMesssages, footerMessages]);
expect(initializeArg.requireAuthenticatedUser).toEqual(true);
});
});

View File

@@ -1,7 +1,6 @@
import InfoPopover from 'components/InfoPopover/messages';
import ResponseDisplay from 'containers/ResponseDisplay/messages';
import ResponseDisplayComponents from 'containers/ResponseDisplay/components/messages';
import CourseHeader from 'containers/CourseHeader/messages';
import CriterionContainer from 'containers/CriterionContainer/messages';
import ListView from 'containers/ListView/messages';
import ReviewActions from 'containers/ReviewActions/messages';
@@ -20,7 +19,6 @@ export default {
InfoPopover: mapMessages(InfoPopover),
ResponseDisplay: mapMessages(ResponseDisplay),
ResponseDisplayComponents: mapMessages(ResponseDisplayComponents),
CourseHeader: mapMessages(CourseHeader),
CriterionContainer: mapMessages(CriterionContainer),
ListView: mapMessages(ListView),
ReviewActions: mapMessages(ReviewActions),