Modernize build and fix the anonymous header (#26)

* refactor: rename SiteHeader to Header

* build: use frontend-build

* fix: ensure that we can handle anonymous users

* feat: anonymous header works now

Up until now we haven’t had any microfrontends that allowed anonymous usage.  The Header was encountering errors rendering in such situations, though it was close.  Now it properly shows the Login and Sign Up buttons.

* build: bump version of frontend-build
This commit is contained in:
David Joy
2019-11-04 09:58:36 -05:00
committed by GitHub
parent 5d77fab8f3
commit 57e05c4d88
25 changed files with 9635 additions and 8221 deletions

View File

@@ -8,7 +8,7 @@ import Avatar from './Avatar';
import { LinkedLogo, Logo } from './Logo';
// i18n
import messages from './SiteHeader.messages';
import messages from './Header.messages';
// Assets
import { CaretIcon } from './Icons';

View File

@@ -8,7 +8,7 @@ import MobileHeader from './MobileHeader';
import LogoSVG from './logo.svg';
import messages from './SiteHeader.messages';
import messages from './Header.messages';
App.ensureConfig([
'LMS_BASE_URL',
@@ -17,7 +17,7 @@ App.ensureConfig([
'SITE_NAME',
], 'Header component');
function SiteHeader({ intl }) {
function Header({ intl }) {
const { authenticatedUser, config } = useContext(AppContext);
const mainMenu = [
@@ -28,7 +28,7 @@ function SiteHeader({ intl }) {
},
];
const userMenu = [
const userMenu = authenticatedUser === null ? [] : [
{
type: 'item',
href: `${config.LMS_BASE_URL}/dashboard`,
@@ -69,9 +69,9 @@ function SiteHeader({ intl }) {
logoAltText: config.SITE_NAME,
siteName: config.SITE_NAME,
logoDestination: `${config.LMS_BASE_URL}/dashboard`,
loggedIn: !!authenticatedUser.username,
username: authenticatedUser.username,
avatar: authenticatedUser.avatar,
loggedIn: authenticatedUser !== null,
username: authenticatedUser !== null ? authenticatedUser.username : null,
avatar: authenticatedUser !== null ? authenticatedUser.avatar : null,
mainMenu,
userMenu,
loggedOutItems,
@@ -89,8 +89,8 @@ function SiteHeader({ intl }) {
);
}
SiteHeader.propTypes = {
Header.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(SiteHeader);
export default injectIntl(Header);

120
src/Header.test.jsx Normal file
View File

@@ -0,0 +1,120 @@
import React from 'react';
import { IntlProvider } from '@edx/frontend-i18n';
import TestRenderer from 'react-test-renderer';
import { AppContext } from '@edx/frontend-base';
import { Context as ResponsiveContext } from 'react-responsive';
import Header from './index';
describe('<Header />', () => {
it('renders correctly for anonymous desktop', () => {
const component = (
<ResponsiveContext.Provider value={{ width: 1280 }}>
<IntlProvider locale="en" messages={{}}>
<AppContext.Provider value={{
authenticatedUser: null,
config: {
LMS_BASE_URL: process.env.LMS_BASE_URL,
SITE_NAME: process.env.SITE_NAME,
LOGIN_URL: process.env.LOGIN_URL,
LOGOUT_URL: process.env.LOGOUT_URL,
},
}}
>
<Header />
</AppContext.Provider>
</IntlProvider>
</ResponsiveContext.Provider>
);
const wrapper = TestRenderer.create(component);
expect(wrapper.toJSON()).toMatchSnapshot();
});
it('renders correctly for authenticated desktop', () => {
const component = (
<ResponsiveContext.Provider value={{ width: 1280 }}>
<IntlProvider locale="en" messages={{}}>
<AppContext.Provider value={{
authenticatedUser: {
userId: 'abc123',
username: 'edX',
roles: [],
administrator: false,
},
config: {
LMS_BASE_URL: process.env.LMS_BASE_URL,
SITE_NAME: process.env.SITE_NAME,
LOGIN_URL: process.env.LOGIN_URL,
LOGOUT_URL: process.env.LOGOUT_URL,
},
}}
>
<Header />
</AppContext.Provider>
</IntlProvider>
</ResponsiveContext.Provider>
);
const wrapper = TestRenderer.create(component);
expect(wrapper.toJSON()).toMatchSnapshot();
});
it('renders correctly for anonymous mobile', () => {
const component = (
<ResponsiveContext.Provider value={{ width: 500 }}>
<IntlProvider locale="en" messages={{}}>
<AppContext.Provider value={{
authenticatedUser: null,
config: {
LMS_BASE_URL: process.env.LMS_BASE_URL,
SITE_NAME: process.env.SITE_NAME,
LOGIN_URL: process.env.LOGIN_URL,
LOGOUT_URL: process.env.LOGOUT_URL,
},
}}
>
<Header />
</AppContext.Provider>
</IntlProvider>
</ResponsiveContext.Provider>
);
const wrapper = TestRenderer.create(component);
expect(wrapper.toJSON()).toMatchSnapshot();
});
it('renders correctly for authenticated mobile', () => {
const component = (
<ResponsiveContext.Provider value={{ width: 500 }}>
<IntlProvider locale="en" messages={{}}>
<AppContext.Provider value={{
authenticatedUser: {
userId: 'abc123',
username: 'edX',
roles: [],
administrator: false,
},
config: {
LMS_BASE_URL: process.env.LMS_BASE_URL,
SITE_NAME: process.env.SITE_NAME,
LOGIN_URL: process.env.LOGIN_URL,
LOGOUT_URL: process.env.LOGOUT_URL,
},
}}
>
<Header />
</AppContext.Provider>
</IntlProvider>
</ResponsiveContext.Provider>
);
const wrapper = TestRenderer.create(component);
expect(wrapper.toJSON()).toMatchSnapshot();
});
});

View File

@@ -8,7 +8,7 @@ import Avatar from './Avatar';
import { LinkedLogo, Logo } from './Logo';
// i18n
import messages from './SiteHeader.messages';
import messages from './Header.messages';
// Assets
import { MenuIcon } from './Icons';

View File

@@ -1,70 +0,0 @@
import React from 'react';
import { IntlProvider } from '@edx/frontend-i18n';
import TestRenderer from 'react-test-renderer';
import { AppContext } from '@edx/frontend-base';
import { Context as ResponsiveContext } from 'react-responsive';
import SiteHeader from './index';
describe('<SiteHeader />', () => {
it('renders correctly for desktop', () => {
const component = (
<ResponsiveContext.Provider value={{ width: 1280 }}>
<IntlProvider locale="en" messages={{}}>
<AppContext.Provider value={{
authenticatedUser: {
userId: null,
username: null,
roles: [],
administrator: false,
},
config: {
LMS_BASE_URL: process.env.LMS_BASE_URL,
SITE_NAME: process.env.SITE_NAME,
LOGIN_URL: process.env.LOGIN_URL,
LOGOUT_URL: process.env.LOGOUT_URL,
},
}}
>
<SiteHeader />
</AppContext.Provider>
</IntlProvider>
</ResponsiveContext.Provider>
);
const wrapper = TestRenderer.create(component);
expect(wrapper.toJSON()).toMatchSnapshot();
});
it('renders correctly for mobile', () => {
const component = (
<ResponsiveContext.Provider value={{ width: 500 }}>
<IntlProvider locale="en" messages={{}}>
<AppContext.Provider value={{
authenticatedUser: {
userId: null,
username: null,
roles: [],
administrator: false,
},
config: {
LMS_BASE_URL: process.env.LMS_BASE_URL,
SITE_NAME: process.env.SITE_NAME,
LOGIN_URL: process.env.LOGIN_URL,
LOGOUT_URL: process.env.LOGOUT_URL,
},
}}
>
<SiteHeader />
</AppContext.Provider>
</IntlProvider>
</ResponsiveContext.Provider>
);
const wrapper = TestRenderer.create(component);
expect(wrapper.toJSON()).toMatchSnapshot();
});
});

View File

@@ -1,3 +0,0 @@
// __mocks__/fileMock.js
module.exports = 'test-file-stub';

View File

@@ -0,0 +1,422 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Header /> renders correctly for anonymous desktop 1`] = `
<header
className="site-header-desktop"
>
<div
className="container-fluid"
>
<div
className="nav-container position-relative d-flex align-items-center"
>
<a
className="logo"
href="http://localhost:18000/dashboard"
>
<img
alt="edX"
className="d-block"
src="icon/mock/path"
/>
</a>
<nav
aria-label="Main"
className="nav main-nav"
>
<a
className="nav-link"
href="http://localhost:18000/dashboard"
>
Courses
</a>
</nav>
<nav
aria-label="Secondary"
className="nav secondary-menu-container align-items-center ml-auto"
>
<a
className="btn mr-2 btn-link"
href="http://localhost:18000/login"
>
Login
</a>
<a
className="btn mr-2 btn-outline-primary"
href="http://localhost:18000/register"
>
Sign Up
</a>
</nav>
</div>
</div>
</header>
`;
exports[`<Header /> renders correctly for anonymous mobile 1`] = `
<header
aria-label="Main"
className="site-header-mobile d-flex justify-content-between align-items-center shadow sticky-top"
>
<div
className="w-100 d-flex justify-content-start"
>
<div
className="menu position-static"
onKeyDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<button
aria-expanded={false}
aria-haspopup="menu"
aria-label="Main Menu"
className="menu-trigger icon-button"
onClick={[Function]}
title="Main Menu"
>
<svg
aria-hidden={true}
focusable="false"
height="24px"
role="img"
style={
Object {
"height": "1.5rem",
"width": "1.5rem",
}
}
version="1.1"
viewBox="0 0 24 24"
width="24px"
>
<rect
fill="currentColor"
height="2"
width="20"
x="2"
y="5"
/>
<rect
fill="currentColor"
height="2"
width="20"
x="2"
y="11"
/>
<rect
fill="currentColor"
height="2"
width="20"
x="2"
y="17"
/>
</svg>
</button>
</div>
</div>
<div
className="w-100 d-flex justify-content-center"
>
<a
className="logo"
href="http://localhost:18000/dashboard"
itemType="http://schema.org/Organization"
>
<img
alt="edX"
className="d-block"
src="icon/mock/path"
/>
</a>
</div>
<div
className="w-100 d-flex justify-content-end align-items-center"
>
<nav
aria-label="Secondary"
className="menu position-static"
onKeyDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<button
aria-expanded={false}
aria-haspopup="menu"
aria-label="Account Menu"
className="menu-trigger icon-button"
onClick={[Function]}
title="Account Menu"
>
<span
className="avatar overflow-hidden d-inline-flex rounded-circle null"
style={
Object {
"height": "1.5rem",
"width": "1.5rem",
}
}
>
<svg
aria-hidden={true}
className="text-muted"
focusable="false"
height="24px"
role="img"
style={
Object {
"height": "1.5rem",
"width": "1.5rem",
}
}
version="1.1"
viewBox="0 0 24 24"
width="24px"
>
<path
d="M4.10255106,18.1351061 C4.7170266,16.0581859 8.01891846,14.4720277 12,14.4720277 C15.9810815,14.4720277 19.2829734,16.0581859 19.8974489,18.1351061 C21.215206,16.4412566 22,14.3122775 22,12 C22,6.4771525 17.5228475,2 12,2 C6.4771525,2 2,6.4771525 2,12 C2,14.3122775 2.78479405,16.4412566 4.10255106,18.1351061 Z M12,24 C5.372583,24 0,18.627417 0,12 C0,5.372583 5.372583,0 12,0 C18.627417,0 24,5.372583 24,12 C24,18.627417 18.627417,24 12,24 Z M12,13 C9.790861,13 8,11.209139 8,9 C8,6.790861 9.790861,5 12,5 C14.209139,5 16,6.790861 16,9 C16,11.209139 14.209139,13 12,13 Z"
fill="currentColor"
/>
</svg>
</span>
</button>
</nav>
</div>
</header>
`;
exports[`<Header /> renders correctly for authenticated desktop 1`] = `
<header
className="site-header-desktop"
>
<div
className="container-fluid"
>
<div
className="nav-container position-relative d-flex align-items-center"
>
<a
className="logo"
href="http://localhost:18000/dashboard"
>
<img
alt="edX"
className="d-block"
src="icon/mock/path"
/>
</a>
<nav
aria-label="Main"
className="nav main-nav"
>
<a
className="nav-link"
href="http://localhost:18000/dashboard"
>
Courses
</a>
</nav>
<nav
aria-label="Secondary"
className="nav secondary-menu-container align-items-center ml-auto"
>
<div
className="menu null"
onKeyDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<button
aria-expanded={false}
aria-haspopup="menu"
aria-label="Account menu for edX"
className="menu-trigger btn btn-light d-inline-flex align-items-center pl-2 pr-3"
onClick={[Function]}
>
<span
className="avatar overflow-hidden d-inline-flex rounded-circle mr-2"
style={
Object {
"height": "1.5em",
"width": "1.5em",
}
}
>
<svg
aria-hidden={true}
className="text-muted"
focusable="false"
height="24px"
role="img"
style={
Object {
"height": "1.5em",
"width": "1.5em",
}
}
version="1.1"
viewBox="0 0 24 24"
width="24px"
>
<path
d="M4.10255106,18.1351061 C4.7170266,16.0581859 8.01891846,14.4720277 12,14.4720277 C15.9810815,14.4720277 19.2829734,16.0581859 19.8974489,18.1351061 C21.215206,16.4412566 22,14.3122775 22,12 C22,6.4771525 17.5228475,2 12,2 C6.4771525,2 2,6.4771525 2,12 C2,14.3122775 2.78479405,16.4412566 4.10255106,18.1351061 Z M12,24 C5.372583,24 0,18.627417 0,12 C0,5.372583 5.372583,0 12,0 C18.627417,0 24,5.372583 24,12 C24,18.627417 18.627417,24 12,24 Z M12,13 C9.790861,13 8,11.209139 8,9 C8,6.790861 9.790861,5 12,5 C14.209139,5 16,6.790861 16,9 C16,11.209139 14.209139,13 12,13 Z"
fill="currentColor"
/>
</svg>
</span>
edX
<svg
aria-hidden={true}
focusable="false"
height="16px"
role="img"
version="1.1"
viewBox="0 0 16 16"
width="16px"
>
<path
d="M7,4 L7,8 L11,8 L11,10 L5,10 L5,4 L7,4 Z"
fill="currentColor"
transform="translate(8.000000, 7.000000) rotate(-45.000000) translate(-8.000000, -7.000000) "
/>
</svg>
</button>
</div>
</nav>
</div>
</div>
</header>
`;
exports[`<Header /> renders correctly for authenticated mobile 1`] = `
<header
aria-label="Main"
className="site-header-mobile d-flex justify-content-between align-items-center shadow sticky-top"
>
<div
className="w-100 d-flex justify-content-start"
>
<div
className="menu position-static"
onKeyDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<button
aria-expanded={false}
aria-haspopup="menu"
aria-label="Main Menu"
className="menu-trigger icon-button"
onClick={[Function]}
title="Main Menu"
>
<svg
aria-hidden={true}
focusable="false"
height="24px"
role="img"
style={
Object {
"height": "1.5rem",
"width": "1.5rem",
}
}
version="1.1"
viewBox="0 0 24 24"
width="24px"
>
<rect
fill="currentColor"
height="2"
width="20"
x="2"
y="5"
/>
<rect
fill="currentColor"
height="2"
width="20"
x="2"
y="11"
/>
<rect
fill="currentColor"
height="2"
width="20"
x="2"
y="17"
/>
</svg>
</button>
</div>
</div>
<div
className="w-100 d-flex justify-content-center"
>
<a
className="logo"
href="http://localhost:18000/dashboard"
itemType="http://schema.org/Organization"
>
<img
alt="edX"
className="d-block"
src="icon/mock/path"
/>
</a>
</div>
<div
className="w-100 d-flex justify-content-end align-items-center"
>
<nav
aria-label="Secondary"
className="menu position-static"
onKeyDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<button
aria-expanded={false}
aria-haspopup="menu"
aria-label="Account Menu"
className="menu-trigger icon-button"
onClick={[Function]}
title="Account Menu"
>
<span
className="avatar overflow-hidden d-inline-flex rounded-circle null"
style={
Object {
"height": "1.5rem",
"width": "1.5rem",
}
}
>
<svg
aria-hidden={true}
className="text-muted"
focusable="false"
height="24px"
role="img"
style={
Object {
"height": "1.5rem",
"width": "1.5rem",
}
}
version="1.1"
viewBox="0 0 24 24"
width="24px"
>
<path
d="M4.10255106,18.1351061 C4.7170266,16.0581859 8.01891846,14.4720277 12,14.4720277 C15.9810815,14.4720277 19.2829734,16.0581859 19.8974489,18.1351061 C21.215206,16.4412566 22,14.3122775 22,12 C22,6.4771525 17.5228475,2 12,2 C6.4771525,2 2,6.4771525 2,12 C2,14.3122775 2.78479405,16.4412566 4.10255106,18.1351061 Z M12,24 C5.372583,24 0,18.627417 0,12 C0,5.372583 5.372583,0 12,0 C18.627417,0 24,5.372583 24,12 C24,18.627417 18.627417,24 12,24 Z M12,13 C9.790861,13 8,11.209139 8,9 C8,6.790861 9.790861,5 12,5 C14.209139,5 16,6.790861 16,9 C16,11.209139 14.209139,13 12,13 Z"
fill="currentColor"
/>
</svg>
</span>
</button>
</nav>
</div>
</header>
`;

View File

@@ -1,186 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<SiteHeader /> renders correctly for desktop 1`] = `
<header
className="site-header-desktop"
>
<div
className="container-fluid"
>
<div
className="nav-container position-relative d-flex align-items-center"
>
<a
className="logo"
href="http://localhost:18000/dashboard"
>
<img
alt="edX"
className="d-block"
src="test-file-stub"
/>
</a>
<nav
aria-label="Main"
className="nav main-nav"
>
<a
className="nav-link"
href="http://localhost:18000/dashboard"
>
Courses
</a>
</nav>
<nav
aria-label="Secondary"
className="nav secondary-menu-container align-items-center ml-auto"
>
<a
className="btn mr-2 btn-link"
href="http://localhost:18000/login"
>
Login
</a>
<a
className="btn mr-2 btn-outline-primary"
href="http://localhost:18000/register"
>
Sign Up
</a>
</nav>
</div>
</div>
</header>
`;
exports[`<SiteHeader /> renders correctly for mobile 1`] = `
<header
aria-label="Main"
className="site-header-mobile d-flex justify-content-between align-items-center shadow sticky-top"
>
<div
className="w-100 d-flex justify-content-start"
>
<div
className="menu position-static"
onKeyDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<button
aria-expanded={false}
aria-haspopup="menu"
aria-label="Main Menu"
className="menu-trigger icon-button"
onClick={[Function]}
title="Main Menu"
>
<svg
aria-hidden={true}
focusable="false"
height="24px"
role="img"
style={
Object {
"height": "1.5rem",
"width": "1.5rem",
}
}
version="1.1"
viewBox="0 0 24 24"
width="24px"
>
<rect
fill="currentColor"
height="2"
width="20"
x="2"
y="5"
/>
<rect
fill="currentColor"
height="2"
width="20"
x="2"
y="11"
/>
<rect
fill="currentColor"
height="2"
width="20"
x="2"
y="17"
/>
</svg>
</button>
</div>
</div>
<div
className="w-100 d-flex justify-content-center"
>
<a
className="logo"
href="http://localhost:18000/dashboard"
itemType="http://schema.org/Organization"
>
<img
alt="edX"
className="d-block"
src="test-file-stub"
/>
</a>
</div>
<div
className="w-100 d-flex justify-content-end align-items-center"
>
<nav
aria-label="Secondary"
className="menu position-static"
onKeyDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<button
aria-expanded={false}
aria-haspopup="menu"
aria-label="Account Menu"
className="menu-trigger icon-button"
onClick={[Function]}
title="Account Menu"
>
<span
className="avatar overflow-hidden d-inline-flex rounded-circle null"
style={
Object {
"height": "1.5rem",
"width": "1.5rem",
}
}
>
<svg
aria-hidden={true}
className="text-muted"
focusable="false"
height="24px"
role="img"
style={
Object {
"height": "1.5rem",
"width": "1.5rem",
}
}
version="1.1"
viewBox="0 0 24 24"
width="24px"
>
<path
d="M4.10255106,18.1351061 C4.7170266,16.0581859 8.01891846,14.4720277 12,14.4720277 C15.9810815,14.4720277 19.2829734,16.0581859 19.8974489,18.1351061 C21.215206,16.4412566 22,14.3122775 22,12 C22,6.4771525 17.5228475,2 12,2 C6.4771525,2 2,6.4771525 2,12 C2,14.3122775 2.78479405,16.4412566 4.10255106,18.1351061 Z M12,24 C5.372583,24 0,18.627417 0,12 C0,5.372583 5.372583,0 12,0 C18.627417,0 24,5.372583 24,12 C24,18.627417 18.627417,24 12,24 Z M12,13 C9.790861,13 8,11.209139 8,9 C8,6.790861 9.790861,5 12,5 C14.209139,5 16,6.790861 16,9 C16,11.209139 14.209139,13 12,13 Z"
fill="currentColor"
/>
</svg>
</span>
</button>
</nav>
</div>
</header>
`;

View File

@@ -1,6 +1,6 @@
import SiteHeader from './SiteHeader';
import Header from './Header';
import messages from './i18n/index';
export { messages };
export default SiteHeader;
export default Header;

View File

@@ -11,7 +11,6 @@ Enzyme.configure({ adapter: new Adapter() });
process.env.ACCESS_TOKEN_COOKIE_NAME = 'edx-jwt-cookie-header-payload';
process.env.BASE_URL = 'localhost:1995';
process.env.CREDENTIALS_BASE_URL = 'http://localhost:18150';
process.env.CSRF_COOKIE_NAME = 'csrftoken';
process.env.CSRF_TOKEN_API_PATH = '/csrf/api/v1/token';
process.env.ECOMMERCE_BASE_URL = 'http://localhost:18130';
process.env.LANGUAGE_PREFERENCE_COOKIE_NAME = 'openedx-language-preference';