Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/components/Footer.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { AppContext } from '@edx/frontend-platform/react';

import Footer from './Footer';
import FooterSlot from '../plugin-slots/FooterSlot';
import StudioFooterHelpSectionSlot from '../plugin-slots/StudioFooterHelpSectionSlot';

const FooterWithContext = ({ locale = 'es' }) => {
const contextValue = useMemo(() => ({
Expand Down Expand Up @@ -90,3 +91,32 @@ describe('<Footer />', () => {
});
});
});

describe('<StudioFooterHelpSectionSlot />', () => {
const SectionWithContext = ({ locale = 'es' }) => {
const contextValue = useMemo(() => ({
authenticatedUser: null,
config: {
LOGO_TRADEMARK_URL: process.env.LOGO_TRADEMARK_URL,
LMS_BASE_URL: process.env.LMS_BASE_URL,
},
}), []);

return (
<IntlProvider locale={locale}>
<AppContext.Provider
value={contextValue}
>
<StudioFooterHelpSectionSlot />
</AppContext.Provider>
</IntlProvider>
);
};

it('renders correctly', () => {
const tree = renderer
.create(<SectionWithContext />)
.toJSON();
expect(tree).toMatchSnapshot();
});
});
65 changes: 65 additions & 0 deletions src/components/__snapshots__/Footer.test.jsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,68 @@ exports[`<Footer /> renders correctly renders without a language selector in es
</div>
</footer>
`;

exports[`<StudioFooterHelpSectionSlot /> renders correctly 1`] = `
[
<div
className="m-0 mt-6 row align-items-center justify-content-center"
>
<div
className="col border-top mr-2"
/>
<button
className="btn btn-outline-primary btn-sm"
data-testid="helpToggleButton"
disabled={false}
onClick={[Function]}
type="button"
>
<span
className="pgn__icon pgn__icon__sm btn-icon-before"
>
<svg
aria-hidden={true}
fill="none"
focusable={false}
height={24}
role="img"
viewBox="0 0 24 24"
width={24}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2Zm1 17h-2v-2h2v2Zm2.07-7.75-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25Z"
fill="currentColor"
/>
</svg>
</span>
Looking for help with Studio?
<span
className="pgn__icon pgn__icon__sm btn-icon-after"
>
<svg
aria-hidden={true}
fill="none"
focusable={false}
height={24}
role="img"
viewBox="0 0 24 24"
width={24}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M16.59 8.59 12 13.17 7.41 8.59 6 10l6 6 6-6-1.41-1.41Z"
fill="currentColor"
/>
</svg>
</span>
</button>
<div
className="col border-top ml-2"
/>
</div>,
<div
className="px-4 container-mw-xl container-fluid"
/>,
]
`;
66 changes: 3 additions & 63 deletions src/components/studio-footer/StudioFooter.jsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import React, { useContext, useState } from 'react';
import React, { useContext } from 'react';
import isEmpty from 'lodash/isEmpty';
import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
import { ensureConfig } from '@edx/frontend-platform';
import { AppContext } from '@edx/frontend-platform/react';
import {
ActionRow,
Button,
Container,
Hyperlink,
TransitionReplace,
} from '@openedx/paragon';
import { ExpandLess, ExpandMore, Help } from '@openedx/paragon/icons';
import classNames from 'classnames';
import PropTypes from 'prop-types';

import messages from './messages';
import StudioFooterLogoSlot from '../../plugin-slots/StudioFooterLogoSlot';
import StudioFooterHelpSectionSlot from '../../plugin-slots/StudioFooterHelpSectionSlot';

ensureConfig([
'LMS_BASE_URL',
Expand All @@ -32,76 +30,18 @@ const StudioFooter = ({
containerProps,
}) => {
const intl = useIntl();
const [isOpen, setIsOpen] = useState(false);
const { config } = useContext(AppContext);

const { containerClassName, ...restContainerProps } = containerProps || {};

return (
<>
<div className="m-0 mt-6 row align-items-center justify-content-center">
<div className="col border-top mr-2" />
<Button
data-testid="helpToggleButton"
variant="outline-primary"
onClick={() => setIsOpen(!isOpen)}
iconBefore={Help}
iconAfter={isOpen ? ExpandLess : ExpandMore}
size="sm"
>
{isOpen ? intl.formatMessage(messages.closeHelpButtonLabel)
: intl.formatMessage(messages.openHelpButtonLabel)}
</Button>
<div className="col border-top ml-2" />
</div>
<StudioFooterHelpSectionSlot containerProps={containerProps} />
<Container
size="xl"
className={classNames('px-4', containerClassName)}
{...restContainerProps}
>
<TransitionReplace>
{isOpen ? (
<ActionRow key="help-link-button-row" className="py-4" data-testid="helpButtonRow">
<ActionRow.Spacer />
<Button as="a" href="https://docs.openedx.org/" size="sm">
<FormattedMessage {...messages.edxDocumentationButtonLabel} />
</Button>
<Button
as="a"
href="https://openedx.org/"
size="sm"
data-testid="openEdXPortalButton"
>
<FormattedMessage {...messages.openEdxPortalButtonLabel} />
</Button>
<Button
as="a"
href="https://www.edx.org/course/edx101-overview-of-creating-an-edx-course#.VO4eaLPF-n1"
size="sm"
>
<FormattedMessage {...messages.edx101ButtonLabel} />
</Button>
<Button
as="a"
href="https://www.edx.org/course/studiox-creating-a-course-with-edx-studio"
size="sm"
>
<FormattedMessage {...messages.studioXButtonLabel} />
</Button>
{!isEmpty(config.SUPPORT_EMAIL) && (
<Button
as="a"
href={`mailto:${config.SUPPORT_EMAIL}`}
size="sm"
data-testid="contactUsButton"
>
<FormattedMessage {...messages.contactUsButtonLabel} />
</Button>
)}
<ActionRow.Spacer />
</ActionRow>
) : null}
</TransitionReplace>
<ActionRow className="pt-3 m-0 x-small">
© {new Date().getFullYear()} <Hyperlink destination={config.MARKETING_SITE_BASE_URL} target="_blank" className="ml-2">{config.SITE_NAME}</Hyperlink>
<ActionRow.Spacer />
Expand Down
3 changes: 2 additions & 1 deletion src/components/studio-footer/StudioFooter.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, { useMemo } from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom/extend-expect';
import '@testing-library/jest-dom';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { AppContext } from '@edx/frontend-platform/react';
import StudioFooterSlot from '../../plugin-slots/StudioFooterSlot';
Expand Down Expand Up @@ -77,7 +78,7 @@ describe('Footer', () => {
render(<Component />);
const helpToggleButton = screen.getByText(messages.openHelpButtonLabel.defaultMessage);
await user.click(helpToggleButton);
expect(screen.getByTestId('openEdXPortalButton')).toBeVisible();
expect(screen.getByTestId('openEdXDemoCourseButton')).toBeVisible();
});
it('should not show contact us button', async () => {
const user = userEvent.setup();
Expand Down
32 changes: 32 additions & 0 deletions src/components/studio-footer/help-components/HelpButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
Button,
} from '@openedx/paragon';
import { ExpandLess, ExpandMore, Help } from '@openedx/paragon/icons';
import PropTypes from 'prop-types';
import messages from '../messages';

const HelpButton = ({ isOpen, setIsOpen }) => {
const intl = useIntl();
return (
<Button
data-testid="helpToggleButton"
variant="outline-primary"
onClick={() => setIsOpen(!isOpen)}
iconBefore={Help}
iconAfter={isOpen ? ExpandLess : ExpandMore}
size="sm"
>
{isOpen ? intl.formatMessage(messages.closeHelpButtonLabel)
: intl.formatMessage(messages.openHelpButtonLabel)}
</Button>
);
};

HelpButton.propTypes = {
setIsOpen: PropTypes.func.isRequired,
isOpen: PropTypes.bool.isRequired,
};

export default HelpButton;
55 changes: 55 additions & 0 deletions src/components/studio-footer/help-components/HelpButton.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { useMemo } from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { AppContext } from '@edx/frontend-platform/react';
import HelpButton from './HelpButton';
import '@testing-library/jest-dom';

// eslint-disable-next-line react/prop-types
const ButtonWithContext = ({ locale = 'en', isOpen, setIsOpen }) => {
const contextValue = useMemo(() => ({
authenticatedUser: null,
config: { },
}), []);

return (
<IntlProvider locale={locale}>
<AppContext.Provider
value={contextValue}
>
<HelpButton isOpen={isOpen} setIsOpen={setIsOpen} />
</AppContext.Provider>
</IntlProvider>
);
};

describe('HelpButton', () => {
const mockSetIsOpen = jest.fn();

beforeEach(() => {
mockSetIsOpen.mockClear();
});

it('renders with "open" label when isOpen is false', () => {
render(<ButtonWithContext isOpen={false} setIsOpen={mockSetIsOpen} />);
expect(screen.getByTestId('helpToggleButton')).toBeInTheDocument();
expect(screen.getByRole('button')).toHaveTextContent(/help|open/i);
});

it('renders with "close" label when isOpen is true', () => {
render(<ButtonWithContext isOpen setIsOpen={mockSetIsOpen} />);
expect(screen.getByTestId('helpToggleButton')).toBeInTheDocument();
expect(screen.getByRole('button')).toHaveTextContent(/close|help/i);
});

it('calls setIsOpen with the toggled value when clicked', async () => {
const user = userEvent.setup();
const { rerender } = render(<ButtonWithContext isOpen={false} setIsOpen={mockSetIsOpen} />);
await user.click(screen.getByTestId('helpToggleButton'));
expect(mockSetIsOpen).toHaveBeenCalledWith(true);
rerender(<ButtonWithContext isOpen setIsOpen={mockSetIsOpen} />);
await user.click(screen.getByTestId('helpToggleButton'));
expect(mockSetIsOpen).toHaveBeenCalledWith(false);
});
});
76 changes: 76 additions & 0 deletions src/components/studio-footer/help-components/HelpContent.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React, { useContext } from 'react';
import { AppContext } from '@edx/frontend-platform/react';
import isEmpty from 'lodash/isEmpty';
import { ensureConfig } from '@edx/frontend-platform';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import {
ActionRow,
Button,
} from '@openedx/paragon';
import messages from '../messages';

ensureConfig([
'SUPPORT_EMAIL',
], 'Studio Footer Help Content component');

const BUTTONS = [
{
as: 'a',
href: 'https://docs.openedx.org/',
size: 'sm',
message: messages.edxDocumentationButtonLabel,
dataTestid: null,
},
{
as: 'a',
href: 'https://openedx.org/',
size: 'sm',
message: messages.openEdxPortalButtonLabel,
dataTestid: 'openEdXPortalButton',
},
{
as: 'a',
href: 'https://www.edx.org/course/edx101-overview-of-creating-an-edx-course#.VO4eaLPF-n1',
size: 'sm',
message: messages.edx101ButtonLabel,
dataTestid: 'openEdXDemoCourseButton',
},
{
as: 'a',
href: 'https://www.edx.org/course/studiox-creating-a-course-with-edx-studio',
size: 'sm',
message: messages.studioXButtonLabel,
dataTestid: null,
},
];

const HelpContent = () => {
const { config } = useContext(AppContext);
return (
<ActionRow key="help-link-button-row" className="py-4" data-testid="helpButtonRow">
<ActionRow.Spacer />

{BUTTONS.map(({
as, href, size, message, dataTestid,
}) => (
<Button as={as} href={href} size={size} key={message.id} data-testid={dataTestid}>
<FormattedMessage {...message} />
</Button>
))}

{!isEmpty(config.SUPPORT_EMAIL) && (
<Button
as="a"
href={`mailto:${config.SUPPORT_EMAIL}`}
size="sm"
data-testid="contactUsButton"
>
<FormattedMessage {...messages.contactUsButtonLabel} />
</Button>
)}
<ActionRow.Spacer />
</ActionRow>
);
};

export default HelpContent;
Loading