Compare commits

...

5 Commits

Author SHA1 Message Date
Leangseu Kim
dfdcbc0a8d feat: upgrade frontend platform to version 3 2022-11-07 12:45:43 +00:00
renovate[bot]
c3b02a2946 chore(deps): update dependency enzyme-adapter-react-16 to v1.15.7 2022-11-07 09:36:45 +00:00
renovate[bot]
f6c1a8bcc1 chore(deps): update dependency redux-saga to v1.2.1 2022-10-31 07:42:11 +00:00
Bilal Qamar
6c02962e0d refactor: updated frontend-build & resolved eslint issues 2022-10-26 10:37:57 -03:00
renovate[bot]
acaf98f0b1 chore(deps): update dependency @testing-library/dom to v8.19.0 2022-10-24 08:15:34 +00:00
15 changed files with 9967 additions and 8382 deletions

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line import/no-extraneous-dependencies
const { createConfig } = require('@edx/frontend-build'); const { createConfig } = require('@edx/frontend-build');
module.exports = createConfig('eslint'); module.exports = createConfig('eslint');

17918
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -34,13 +34,18 @@
"homepage": "https://github.com/openedx/frontend-component-header#readme", "homepage": "https://github.com/openedx/frontend-component-header#readme",
"devDependencies": { "devDependencies": {
"@edx/brand": "npm:@edx/brand-openedx@1.1.0", "@edx/brand": "npm:@edx/brand-openedx@1.1.0",
"@edx/frontend-build": "11.0.2", "@edx/frontend-build": "12.3.0",
"@edx/frontend-platform": "2.6.2", "@edx/frontend-platform": "^3.0.1",
"@edx/paragon": "19.25.3", "@edx/paragon": "19.25.3",
"@testing-library/dom": "8.19.0",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "10.4.9",
"codecov": "3.8.3", "codecov": "3.8.3",
"enzyme": "3.11.0", "enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.6", "enzyme-adapter-react-16": "1.15.7",
"husky": "7.0.4", "husky": "7.0.4",
"jest": "28.1.3",
"jest-chain": "1.1.6",
"prop-types": "15.8.1", "prop-types": "15.8.1",
"react": "16.14.0", "react": "16.14.0",
"react-dom": "16.14.0", "react-dom": "16.14.0",
@@ -49,25 +54,20 @@
"react-test-renderer": "16.14.0", "react-test-renderer": "16.14.0",
"reactifex": "1.1.1", "reactifex": "1.1.1",
"redux": "4.2.0", "redux": "4.2.0",
"redux-saga": "1.1.3", "redux-saga": "1.2.1"
"@testing-library/dom": "8.18.1",
"@testing-library/jest-dom": "5.16.5",
"jest": "28.1.3",
"jest-chain": "1.1.6",
"@testing-library/react": "10.4.9"
}, },
"dependencies": { "dependencies": {
"babel-polyfill": "6.26.0",
"react-responsive": "8.2.0",
"react-transition-group": "4.4.5",
"@fortawesome/fontawesome-svg-core": "1.2.36", "@fortawesome/fontawesome-svg-core": "1.2.36",
"@fortawesome/free-brands-svg-icons": "5.15.4", "@fortawesome/free-brands-svg-icons": "5.15.4",
"@fortawesome/free-regular-svg-icons": "5.15.4", "@fortawesome/free-regular-svg-icons": "5.15.4",
"@fortawesome/free-solid-svg-icons": "5.15.4", "@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/react-fontawesome": "^0.2.0" "@fortawesome/react-fontawesome": "^0.2.0",
"babel-polyfill": "6.26.0",
"react-responsive": "8.2.0",
"react-transition-group": "4.4.5"
}, },
"peerDependencies": { "peerDependencies": {
"@edx/frontend-platform": "^2.0.0", "@edx/frontend-platform": "^3.0.0",
"@edx/paragon": ">= 7.0.0 < 21.0.0", "@edx/paragon": ">= 7.0.0 < 21.0.0",
"prop-types": "^15.5.10", "prop-types": "^15.5.10",
"react": "^16.9.0", "react": "^16.9.0",

View File

@@ -3,12 +3,12 @@ import PropTypes from 'prop-types';
import { AvatarIcon } from './Icons'; import { AvatarIcon } from './Icons';
function Avatar({ const Avatar = ({
size, size,
src, src,
alt, alt,
className, className,
}) { }) => {
const avatar = src ? ( const avatar = src ? (
<img className="d-block w-100 h-100" src={src} alt={alt} /> <img className="d-block w-100 h-100" src={src} alt={alt} />
) : ( ) : (
@@ -23,7 +23,7 @@ function Avatar({
{avatar} {avatar}
</span> </span>
); );
} };
Avatar.propTypes = { Avatar.propTypes = {
src: PropTypes.string, src: PropTypes.string,

View File

@@ -30,7 +30,7 @@ subscribe(APP_CONFIG_INITIALIZED, () => {
}, 'Header additional config'); }, 'Header additional config');
}); });
function Header({ intl }) { const Header = ({ intl }) => {
const { authenticatedUser, config } = useContext(AppContext); const { authenticatedUser, config } = useContext(AppContext);
const mainMenu = [ const mainMenu = [
@@ -110,7 +110,7 @@ function Header({ intl }) {
</Responsive> </Responsive>
</> </>
); );
} };
Header.propTypes = { Header.propTypes = {
intl: intlShape.isRequired, intl: intlShape.isRequired,

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/prop-types */
import React from 'react'; import React from 'react';
import { IntlProvider } from '@edx/frontend-platform/i18n'; import { IntlProvider } from '@edx/frontend-platform/i18n';
import TestRenderer from 'react-test-renderer'; import TestRenderer from 'react-test-renderer';
@@ -6,28 +7,31 @@ import { Context as ResponsiveContext } from 'react-responsive';
import Header from './index'; import Header from './index';
const HeaderComponent = ({ width, contextValue }) => (
<ResponsiveContext.Provider value={width}>
<IntlProvider locale="en" messages={{}}>
<AppContext.Provider
value={contextValue}
>
<Header />
</AppContext.Provider>
</IntlProvider>
</ResponsiveContext.Provider>
);
describe('<Header />', () => { describe('<Header />', () => {
it('renders correctly for anonymous desktop', () => { it('renders correctly for anonymous desktop', () => {
const component = ( const contextValue = {
<ResponsiveContext.Provider value={{ width: 1280 }}> authenticatedUser: null,
<IntlProvider locale="en" messages={{}}> config: {
<AppContext.Provider LMS_BASE_URL: process.env.LMS_BASE_URL,
value={{ SITE_NAME: process.env.SITE_NAME,
authenticatedUser: null, LOGIN_URL: process.env.LOGIN_URL,
config: { LOGOUT_URL: process.env.LOGOUT_URL,
LMS_BASE_URL: process.env.LMS_BASE_URL, LOGO_URL: process.env.LOGO_URL,
SITE_NAME: process.env.SITE_NAME, },
LOGIN_URL: process.env.LOGIN_URL, };
LOGOUT_URL: process.env.LOGOUT_URL, const component = <HeaderComponent width={{ width: 1280 }} contextValue={contextValue} />;
LOGO_URL: process.env.LOGO_URL,
},
}}
>
<Header />
</AppContext.Provider>
</IntlProvider>
</ResponsiveContext.Provider>
);
const wrapper = TestRenderer.create(component); const wrapper = TestRenderer.create(component);
@@ -35,31 +39,22 @@ describe('<Header />', () => {
}); });
it('renders correctly for authenticated desktop', () => { it('renders correctly for authenticated desktop', () => {
const component = ( const contextValue = {
<ResponsiveContext.Provider value={{ width: 1280 }}> authenticatedUser: {
<IntlProvider locale="en" messages={{}}> userId: 'abc123',
<AppContext.Provider username: 'edX',
value={{ roles: [],
authenticatedUser: { administrator: false,
userId: 'abc123', },
username: 'edX', config: {
roles: [], LMS_BASE_URL: process.env.LMS_BASE_URL,
administrator: false, SITE_NAME: process.env.SITE_NAME,
}, LOGIN_URL: process.env.LOGIN_URL,
config: { LOGOUT_URL: process.env.LOGOUT_URL,
LMS_BASE_URL: process.env.LMS_BASE_URL, LOGO_URL: process.env.LOGO_URL,
SITE_NAME: process.env.SITE_NAME, },
LOGIN_URL: process.env.LOGIN_URL, };
LOGOUT_URL: process.env.LOGOUT_URL, const component = <HeaderComponent width={{ width: 1280 }} contextValue={contextValue} />;
LOGO_URL: process.env.LOGO_URL,
},
}}
>
<Header />
</AppContext.Provider>
</IntlProvider>
</ResponsiveContext.Provider>
);
const wrapper = TestRenderer.create(component); const wrapper = TestRenderer.create(component);
@@ -67,26 +62,17 @@ describe('<Header />', () => {
}); });
it('renders correctly for anonymous mobile', () => { it('renders correctly for anonymous mobile', () => {
const component = ( const contextValue = {
<ResponsiveContext.Provider value={{ width: 500 }}> authenticatedUser: null,
<IntlProvider locale="en" messages={{}}> config: {
<AppContext.Provider LMS_BASE_URL: process.env.LMS_BASE_URL,
value={{ SITE_NAME: process.env.SITE_NAME,
authenticatedUser: null, LOGIN_URL: process.env.LOGIN_URL,
config: { LOGOUT_URL: process.env.LOGOUT_URL,
LMS_BASE_URL: process.env.LMS_BASE_URL, LOGO_URL: process.env.LOGO_URL,
SITE_NAME: process.env.SITE_NAME, },
LOGIN_URL: process.env.LOGIN_URL, };
LOGOUT_URL: process.env.LOGOUT_URL, const component = <HeaderComponent width={{ width: 500 }} contextValue={contextValue} />;
LOGO_URL: process.env.LOGO_URL,
},
}}
>
<Header />
</AppContext.Provider>
</IntlProvider>
</ResponsiveContext.Provider>
);
const wrapper = TestRenderer.create(component); const wrapper = TestRenderer.create(component);
@@ -94,31 +80,22 @@ describe('<Header />', () => {
}); });
it('renders correctly for authenticated mobile', () => { it('renders correctly for authenticated mobile', () => {
const component = ( const contextValue = {
<ResponsiveContext.Provider value={{ width: 500 }}> authenticatedUser: {
<IntlProvider locale="en" messages={{}}> userId: 'abc123',
<AppContext.Provider username: 'edX',
value={{ roles: [],
authenticatedUser: { administrator: false,
userId: 'abc123', },
username: 'edX', config: {
roles: [], LMS_BASE_URL: process.env.LMS_BASE_URL,
administrator: false, SITE_NAME: process.env.SITE_NAME,
}, LOGIN_URL: process.env.LOGIN_URL,
config: { LOGOUT_URL: process.env.LOGOUT_URL,
LMS_BASE_URL: process.env.LMS_BASE_URL, LOGO_URL: process.env.LOGO_URL,
SITE_NAME: process.env.SITE_NAME, },
LOGIN_URL: process.env.LOGIN_URL, };
LOGOUT_URL: process.env.LOGOUT_URL, const component = <HeaderComponent width={{ width: 500 }} contextValue={contextValue} />;
LOGO_URL: process.env.LOGO_URL,
},
}}
>
<Header />
</AppContext.Provider>
</IntlProvider>
</ResponsiveContext.Provider>
);
const wrapper = TestRenderer.create(component); const wrapper = TestRenderer.create(component);

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
export const MenuIcon = props => ( export const MenuIcon = (props) => (
<svg <svg
width="24px" width="24px"
height="24px" height="24px"
@@ -14,7 +14,7 @@ export const MenuIcon = props => (
</svg> </svg>
); );
export const AvatarIcon = props => ( export const AvatarIcon = (props) => (
<svg <svg
width="24px" width="24px"
height="24px" height="24px"
@@ -29,7 +29,7 @@ export const AvatarIcon = props => (
</svg> </svg>
); );
export const CaretIcon = props => ( export const CaretIcon = (props) => (
<svg <svg
width="16px" width="16px"
height="16px" height="16px"

View File

@@ -1,29 +1,25 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
function Logo({ src, alt, ...attributes }) { const Logo = ({ src, alt, ...attributes }) => (
return ( <img src={src} alt={alt} {...attributes} />
<img src={src} alt={alt} {...attributes} /> );
);
}
Logo.propTypes = { Logo.propTypes = {
src: PropTypes.string.isRequired, src: PropTypes.string.isRequired,
alt: PropTypes.string.isRequired, alt: PropTypes.string.isRequired,
}; };
function LinkedLogo({ const LinkedLogo = ({
href, href,
src, src,
alt, alt,
...attributes ...attributes
}) { }) => (
return ( <a href={href} {...attributes}>
<a href={href} {...attributes}> <img className="d-block" src={src} alt={alt} />
<img className="d-block" src={src} alt={alt} /> </a>
</a> );
);
}
LinkedLogo.propTypes = { LinkedLogo.propTypes = {
href: PropTypes.string.isRequired, href: PropTypes.string.isRequired,

View File

@@ -2,12 +2,10 @@ import React from 'react';
import { CSSTransition } from 'react-transition-group'; import { CSSTransition } from 'react-transition-group';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
function MenuTrigger({ tag, className, ...attributes }) { const MenuTrigger = ({ tag, className, ...attributes }) => React.createElement(tag, {
return React.createElement(tag, { className: `menu-trigger ${className}`,
className: `menu-trigger ${className}`, ...attributes,
...attributes, });
});
}
MenuTrigger.propTypes = { MenuTrigger.propTypes = {
tag: PropTypes.string, tag: PropTypes.string,
className: PropTypes.string, className: PropTypes.string,
@@ -18,12 +16,10 @@ MenuTrigger.defaultProps = {
}; };
const MenuTriggerType = <MenuTrigger />.type; const MenuTriggerType = <MenuTrigger />.type;
function MenuContent({ tag, className, ...attributes }) { const MenuContent = ({ tag, className, ...attributes }) => React.createElement(tag, {
return React.createElement(tag, { className: ['menu-content', className].join(' '),
className: ['menu-content', className].join(' '), ...attributes,
...attributes, });
});
}
MenuContent.propTypes = { MenuContent.propTypes = {
tag: PropTypes.string, tag: PropTypes.string,
className: PropTypes.string, className: PropTypes.string,

View File

@@ -151,7 +151,7 @@ StudioDesktopHeaderBase.defaultProps = {
const StudioDesktopHeader = injectIntl(StudioDesktopHeaderBase); const StudioDesktopHeader = injectIntl(StudioDesktopHeaderBase);
function StudioHeader({ intl, actionRowContent }) { const StudioHeader = ({ intl, actionRowContent }) => {
const { authenticatedUser, config } = useContext(AppContext); const { authenticatedUser, config } = useContext(AppContext);
const userMenu = authenticatedUser === null ? [] : [ const userMenu = authenticatedUser === null ? [] : [
@@ -185,7 +185,7 @@ function StudioHeader({ intl, actionRowContent }) {
}; };
return <StudioDesktopHeader {...props} />; return <StudioDesktopHeader {...props} />;
} };
StudioHeader.propTypes = { StudioHeader.propTypes = {
intl: intlShape.isRequired, intl: intlShape.isRequired,
@@ -193,6 +193,7 @@ StudioHeader.propTypes = {
}; };
StudioHeader.defaultProps = { StudioHeader.defaultProps = {
// eslint-disable-next-line react/jsx-no-useless-fragment
actionRowContent: <></>, actionRowContent: <></>,
}; };

View File

@@ -1,4 +1,5 @@
import React from 'react'; /* eslint-disable react/prop-types */
import React, { useMemo } from 'react';
import { IntlProvider } from '@edx/frontend-platform/i18n'; import { IntlProvider } from '@edx/frontend-platform/i18n';
import TestRenderer from 'react-test-renderer'; import TestRenderer from 'react-test-renderer';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
@@ -11,31 +12,62 @@ import {
import { StudioHeader } from './index'; import { StudioHeader } from './index';
const StudioHeaderComponent = ({ contextValue, appMenu = null, mainMenu = [] }) => (
<IntlProvider locale="en" messages={{}}>
<AppContext.Provider
value={contextValue}
>
<StudioHeader appMenu={appMenu} mainMenu={mainMenu} />
</AppContext.Provider>
</IntlProvider>
);
const StudioHeaderContext = ({ actionRowContent = null }) => {
const headerContextValue = useMemo(() => ({
authenticatedUser: {
userId: 'abc123',
username: 'edX',
roles: [],
administrator: false,
},
config: {
STUDIO_BASE_URL: process.env.STUDIO_BASE_URL,
SITE_NAME: process.env.SITE_NAME,
LOGIN_URL: process.env.LOGIN_URL,
LOGOUT_URL: process.env.LOGOUT_URL,
LOGO_URL: process.env.LOGO_URL,
},
}), []);
return (
<IntlProvider locale="en" messages={{}}>
<AppContext.Provider
value={headerContextValue}
>
<StudioHeader actionRowContent={actionRowContent} />
</AppContext.Provider>
</IntlProvider>
);
};
describe('<StudioHeader />', () => { describe('<StudioHeader />', () => {
it('renders correctly', () => { it('renders correctly', () => {
const component = ( const contextValue = {
<IntlProvider locale="en" messages={{}}> authenticatedUser: {
<AppContext.Provider userId: 'abc123',
value={{ username: 'edX',
authenticatedUser: { roles: [],
userId: 'abc123', administrator: false,
username: 'edX', },
roles: [], config: {
administrator: false, STUDIO_BASE_URL: process.env.STUDIO_BASE_URL,
}, SITE_NAME: process.env.SITE_NAME,
config: { LOGIN_URL: process.env.LOGIN_URL,
STUDIO_BASE_URL: process.env.STUDIO_BASE_URL, LOGOUT_URL: process.env.LOGOUT_URL,
SITE_NAME: process.env.SITE_NAME, LOGO_URL: process.env.LOGO_URL,
LOGIN_URL: process.env.LOGIN_URL, },
LOGOUT_URL: process.env.LOGOUT_URL, };
LOGO_URL: process.env.LOGO_URL,
}, const component = <StudioHeaderComponent contextValue={contextValue} />;
}}
>
<StudioHeader />
</AppContext.Provider>
</IntlProvider>
);
const wrapper = TestRenderer.create(component); const wrapper = TestRenderer.create(component);
@@ -67,29 +99,7 @@ describe('<StudioHeader />', () => {
</> </>
); );
const component = ( const component = <StudioHeaderContext actionRowContent={actionRowContent} />;
<IntlProvider locale="en" messages={{}}>
<AppContext.Provider
value={{
authenticatedUser: {
userId: 'abc123',
username: 'edX',
roles: [],
administrator: false,
},
config: {
STUDIO_BASE_URL: process.env.STUDIO_BASE_URL,
SITE_NAME: process.env.SITE_NAME,
LOGIN_URL: process.env.LOGIN_URL,
LOGOUT_URL: process.env.LOGOUT_URL,
LOGO_URL: process.env.LOGO_URL,
},
}}
>
<StudioHeader actionRowContent={actionRowContent} />
</AppContext.Provider>
</IntlProvider>
);
const wrapper = TestRenderer.create(component); const wrapper = TestRenderer.create(component);

View File

@@ -7,25 +7,23 @@ import { Button } from '@edx/paragon';
import genericMessages from '../generic/messages'; import genericMessages from '../generic/messages';
function AnonymousUserMenu({ intl }) { const AnonymousUserMenu = ({ intl }) => (
return ( <div>
<div> <Button
<Button className="mr-3"
className="mr-3" variant="outline-primary"
variant="outline-primary" href={`${getConfig().LMS_BASE_URL}/register?next=${encodeURIComponent(global.location.href)}`}
href={`${getConfig().LMS_BASE_URL}/register?next=${encodeURIComponent(global.location.href)}`} >
> {intl.formatMessage(genericMessages.registerSentenceCase)}
{intl.formatMessage(genericMessages.registerSentenceCase)} </Button>
</Button> <Button
<Button variant="primary"
variant="primary" href={`${getLoginRedirectUrl(global.location.href)}`}
href={`${getLoginRedirectUrl(global.location.href)}`} >
> {intl.formatMessage(genericMessages.signInSentenceCase)}
{intl.formatMessage(genericMessages.signInSentenceCase)} </Button>
</Button> </div>
</div> );
);
}
AnonymousUserMenu.propTypes = { AnonymousUserMenu.propTypes = {
intl: intlShape.isRequired, intl: intlShape.isRequired,

View File

@@ -10,7 +10,7 @@ import { Dropdown } from '@edx/paragon';
import messages from './messages'; import messages from './messages';
function AuthenticatedUserDropdown({ intl, username }) { const AuthenticatedUserDropdown = ({ intl, username }) => {
const dashboardMenuItem = ( const dashboardMenuItem = (
<Dropdown.Item href={`${getConfig().LMS_BASE_URL}/dashboard`}> <Dropdown.Item href={`${getConfig().LMS_BASE_URL}/dashboard`}>
{intl.formatMessage(messages.dashboard)} {intl.formatMessage(messages.dashboard)}
@@ -47,7 +47,7 @@ function AuthenticatedUserDropdown({ intl, username }) {
</Dropdown> </Dropdown>
</> </>
); );
} };
AuthenticatedUserDropdown.propTypes = { AuthenticatedUserDropdown.propTypes = {
intl: intlShape.isRequired, intl: intlShape.isRequired,

View File

@@ -8,18 +8,16 @@ import AnonymousUserMenu from './AnonymousUserMenu';
import AuthenticatedUserDropdown from './AuthenticatedUserDropdown'; import AuthenticatedUserDropdown from './AuthenticatedUserDropdown';
import messages from './messages'; import messages from './messages';
function LinkedLogo({ const LinkedLogo = ({
href, href,
src, src,
alt, alt,
...attributes ...attributes
}) { }) => (
return ( <a href={href} {...attributes}>
<a href={href} {...attributes}> <img className="d-block" src={src} alt={alt} />
<img className="d-block" src={src} alt={alt} /> </a>
</a> );
);
}
LinkedLogo.propTypes = { LinkedLogo.propTypes = {
href: PropTypes.string.isRequired, href: PropTypes.string.isRequired,
@@ -27,9 +25,9 @@ LinkedLogo.propTypes = {
alt: PropTypes.string.isRequired, alt: PropTypes.string.isRequired,
}; };
function LearningHeader({ const LearningHeader = ({
courseOrg, courseNumber, courseTitle, intl, showUserDropdown, courseOrg, courseNumber, courseTitle, intl, showUserDropdown,
}) { }) => {
const { authenticatedUser } = useContext(AppContext); const { authenticatedUser } = useContext(AppContext);
const headerLogo = ( const headerLogo = (
@@ -61,7 +59,7 @@ function LearningHeader({
</div> </div>
</header> </header>
); );
} };
LearningHeader.propTypes = { LearningHeader.propTypes = {
courseOrg: PropTypes.string, courseOrg: PropTypes.string,

View File

@@ -102,16 +102,14 @@ function render(
...renderOptions ...renderOptions
} = {}, } = {},
) { ) {
function Wrapper({ children }) { const Wrapper = ({ children }) => (
return (
// eslint-disable-next-line react/jsx-filename-extension // eslint-disable-next-line react/jsx-filename-extension
<IntlProvider locale="en"> <IntlProvider locale="en">
<AppProvider store={store}> <AppProvider store={store}>
{children} {children}
</AppProvider> </AppProvider>
</IntlProvider> </IntlProvider>
); );
}
Wrapper.propTypes = { Wrapper.propTypes = {
children: PropTypes.node.isRequired, children: PropTypes.node.isRequired,