Skip to content

Commit

Permalink
Allow existing default navigation with custom components
Browse files Browse the repository at this point in the history
REDMINE-20674
  • Loading branch information
tf committed Jul 12, 2024
1 parent 8c1d959 commit b9f5c51
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,27 @@ import React from 'react';

import {DefaultNavigation} from 'widgets/defaultNavigation/DefaultNavigation';

import {useFakeTranslations} from 'pageflow/testHelpers';
import {renderInEntry} from 'pageflow-scrolled/testHelpers';
import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom/extend-expect';

import styles from 'widgets/defaultNavigation/DefaultNavigation.module.css';

describe('DefaultNavigation', () => {
it('does not have style attribute on header by default', async () => {
useFakeTranslations({
'pageflow_scrolled.public.navigation.open_mobile_menu': 'Open mobile menu'
});

it('does not have style attribute on header by default', () => {
const {container} = renderInEntry(
<DefaultNavigation configuration={{}} />
);

expect(container.querySelector('header')).not.toHaveAttribute('style');
});

it('supports overriding accent color', async () => {
it('supports overriding accent color', () => {
const {container} = renderInEntry(
<DefaultNavigation configuration={{accentColor: 'brand-blue'}} />
);
Expand All @@ -23,4 +31,138 @@ describe('DefaultNavigation', () => {
{'--theme-accent-color': 'var(--theme-palette-color-brand-blue)'
});
});

it('supports extra buttons component', () => {
const ExtraButtons = () => <button>Extra</button>;
const {queryByRole} = renderInEntry(
<DefaultNavigation configuration={{}}
ExtraButtons={ExtraButtons} />
);

expect(queryByRole('button', {name: 'Extra'})).not.toBeNull();
});

it('supports alternative mobile menu component', () => {
const MobileMenu = () => <div>Mobile menu</div>;
const {queryByText} = renderInEntry(
<DefaultNavigation configuration={{}}
MobileMenu={MobileMenu} />
);

expect(queryByText('Mobile menu')).not.toBeNull();
});

it('does not render mobile menu button by default', () => {
const {queryByRole} = renderInEntry(
<DefaultNavigation configuration={{}} />
);

expect(queryByRole('button', {name: 'Open mobile menu'})).toBeNull();
});

it('render mobile menu button if chapters are present', () => {
const {queryByRole} = renderInEntry(
<DefaultNavigation configuration={{}} />,
{
seed: {
chapters: [{}, {}]
}
}
);

expect(queryByRole('button', {name: 'Open mobile menu'})).not.toBeNull();
});

it('toggles class to show and hide mobile menu', async () => {
const {getByRole} = renderInEntry(
<DefaultNavigation configuration={{}} />,
{
seed: {
chapters: [{}, {}]
}
}
);

expect(getByRole('navigation')).toHaveClass(styles.navigationChaptersHidden);

const user = userEvent.setup();
await user.click(getByRole('button', {name: 'Open mobile menu'}));

expect(getByRole('navigation')).not.toHaveClass(styles.navigationChaptersHidden);
});

it('keeps mobile menu hidden if custom mobile is present', async () => {
const MobileMenu = () => <div>Mobile menu</div>;
const {getByRole} = renderInEntry(
<DefaultNavigation configuration={{}}
MobileMenu={MobileMenu} />,
{
seed: {
chapters: [{}, {}]
}
}
);

expect(getByRole('navigation')).toHaveClass(styles.navigationChaptersHidden);

const user = userEvent.setup();
await user.click(getByRole('button', {name: 'Open mobile menu'}));

expect(getByRole('navigation')).toHaveClass(styles.navigationChaptersHidden);
});

it('renders mobile menu button if custom mobile menu is present', () => {
const MobileMenu = () => <div>Mobile menu</div>;
const {queryByRole} = renderInEntry(
<DefaultNavigation configuration={{}}
MobileMenu={MobileMenu} />
);

expect(queryByRole('button', {name: 'Open mobile menu'})).not.toBeNull();
});

it('passes open prop to custom mobile menu component', async () => {
const MobileMenu = ({open}) => <div>Mobile menu {open ? 'open' : 'closed'}</div>;
const {queryByText, getByRole} = renderInEntry(
<DefaultNavigation configuration={{}}
MobileMenu={MobileMenu} />
);

expect(queryByText('Mobile menu closed')).not.toBeNull();

const user = userEvent.setup();
await user.click(getByRole('button', {name: 'Open mobile menu'}));

expect(queryByText('Mobile menu open')).not.toBeNull();
});

it('passes close prop to custom mobile menu component', async () => {
const MobileMenu = ({open, close}) => (
<div>
<div>Mobile menu {open ? 'open' : 'closed'}</div>
<button onClick={close}>Close mobile menu</button>;
</div>
);
const {queryByText, getByRole} = renderInEntry(
<DefaultNavigation configuration={{}}
MobileMenu={MobileMenu} />
);

const user = userEvent.setup();
await user.click(getByRole('button', {name: 'Open mobile menu'}));
expect(queryByText('Mobile menu open')).not.toBeNull();

await user.click(getByRole('button', {name: 'Close mobile menu'}));
expect(queryByText('Mobile menu closed')).not.toBeNull();
});

it('passes widget configuration to custom mobile menu component', () => {
const MobileMenu = ({configuration}) => <div>{configuration.mobileMenuTitle}</div>;
const {queryByText} = renderInEntry(
<DefaultNavigation configuration={{mobileMenuTitle: 'Some title'}}
MobileMenu={MobileMenu} />
);

expect(queryByText('Some title')).not.toBeNull();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {Scroller} from './Scroller';

import styles from './DefaultNavigation.module.css';

export function DefaultNavigation({configuration}) {
export function DefaultNavigation({configuration, ExtraButtons, MobileMenu}) {
const [navExpanded, setNavExpanded] = useState(true);
const [mobileNavHidden, setMobileNavHidden] = useState(true);
const [readingProgress, setReadingProgress] = useState(0);
Expand Down Expand Up @@ -104,8 +104,8 @@ export function DefaultNavigation({configuration}) {

return (
<Scroller>
<nav className={classNames(styles.navigationChapters, {[styles.navigationChaptersHidden]: mobileNavHidden})}
role="navigation">
<nav className={classNames(styles.navigationChapters,
{[styles.navigationChaptersHidden]: mobileNavHidden || MobileMenu})}>
<ul className={styles.chapterList}>
{renderChapterLinks(chapters)}
</ul>
Expand All @@ -119,38 +119,42 @@ export function DefaultNavigation({configuration}) {

return (
<>
<header className={classNames(styles.navigationBar, {
[styles.navigationBarExpanded]: (
navExpanded ||
(!isPhonePlatform && configuration.fixedOnDesktop) ||
!mobileNavHidden
),
[styles.hasChapters]: hasChapters
})} style={{'--theme-accent-color': paletteColor(configuration.accentColor)}}>
<div className={styles.navigationBarContentWrapper}>
{hasChapters && <HamburgerIcon onClick={handleBurgerMenuClick}
mobileNavHidden={mobileNavHidden}/>}

<SkipLinks />
<Logo />

{renderNav()}

<div className={classNames(styles.contextIcons)}>
{!configuration.hideToggleMuteButton && <ToggleMuteButton />}
<TranslationsMenu />
{!theme.options.hideLegalInfoButton &&<LegalInfoMenu tooltipOffset={hideSharingButton ? -40 : 0} />}
{!hideSharingButton && <SharingMenu shareProviders={shareProviders} />}
<header className={classNames(styles.navigationBar, {
[styles.navigationBarExpanded]: (
navExpanded ||
(!isPhonePlatform && configuration.fixedOnDesktop) ||
!mobileNavHidden
),
[styles.hasChapters]: hasChapters
})} style={{'--theme-accent-color': paletteColor(configuration.accentColor)}}>
<div className={styles.navigationBarContentWrapper}>
{(hasChapters || MobileMenu) && <HamburgerIcon onClick={handleBurgerMenuClick}
mobileNavHidden={mobileNavHidden}/>}

<SkipLinks />
<Logo />

{renderNav()}
{MobileMenu && <MobileMenu configuration={configuration}
open={!mobileNavHidden}
close={() => setMobileNavHidden(true)} />}

<div className={classNames(styles.contextIcons)}>
{!configuration.hideToggleMuteButton && <ToggleMuteButton />}
<TranslationsMenu />
{!theme.options.hideLegalInfoButton &&<LegalInfoMenu tooltipOffset={hideSharingButton ? -40 : 0} />}
{!hideSharingButton && <SharingMenu shareProviders={shareProviders} />}
{ExtraButtons && <ExtraButtons />}
</div>
</div>
</div>

<div className={styles.progressBar} onMouseEnter={handleProgressBarMouseEnter}>
<span className={styles.progressIndicator} style={{width: readingProgress + '%'}}/>
</div>
</header>
<Widget role="defaultNavigationExtra"
props={{navigationExpanded: navExpanded,
mobileNavigationVisible: !mobileNavHidden}} />

<div className={styles.progressBar} onMouseEnter={handleProgressBarMouseEnter}>
<span className={styles.progressIndicator} style={{width: readingProgress + '%'}}/>
</div>
</header>
<Widget role="defaultNavigationExtra"
props={{navigationExpanded: navExpanded,
mobileNavigationVisible: !mobileNavHidden}} />
</>
);
}

0 comments on commit b9f5c51

Please sign in to comment.