From b9f5c51c10fc1e1ffbe16222634a8a53fedb8f59 Mon Sep 17 00:00:00 2001 From: Tim Fischbach Date: Fri, 12 Jul 2024 17:11:03 +0200 Subject: [PATCH] Allow existing default navigation with custom components REDMINE-20674 --- .../Defaultnavigation-spec.js | 146 +++++++++++++++++- .../defaultNavigation/DefaultNavigation.js | 72 +++++---- 2 files changed, 182 insertions(+), 36 deletions(-) diff --git a/entry_types/scrolled/package/spec/widgets/defaultNavigation/Defaultnavigation-spec.js b/entry_types/scrolled/package/spec/widgets/defaultNavigation/Defaultnavigation-spec.js index 10cbd7ece..c782566ea 100644 --- a/entry_types/scrolled/package/spec/widgets/defaultNavigation/Defaultnavigation-spec.js +++ b/entry_types/scrolled/package/spec/widgets/defaultNavigation/Defaultnavigation-spec.js @@ -2,11 +2,19 @@ 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( ); @@ -14,7 +22,7 @@ describe('DefaultNavigation', () => { expect(container.querySelector('header')).not.toHaveAttribute('style'); }); - it('supports overriding accent color', async () => { + it('supports overriding accent color', () => { const {container} = renderInEntry( ); @@ -23,4 +31,138 @@ describe('DefaultNavigation', () => { {'--theme-accent-color': 'var(--theme-palette-color-brand-blue)' }); }); + + it('supports extra buttons component', () => { + const ExtraButtons = () => ; + const {queryByRole} = renderInEntry( + + ); + + expect(queryByRole('button', {name: 'Extra'})).not.toBeNull(); + }); + + it('supports alternative mobile menu component', () => { + const MobileMenu = () =>
Mobile menu
; + const {queryByText} = renderInEntry( + + ); + + expect(queryByText('Mobile menu')).not.toBeNull(); + }); + + it('does not render mobile menu button by default', () => { + const {queryByRole} = renderInEntry( + + ); + + expect(queryByRole('button', {name: 'Open mobile menu'})).toBeNull(); + }); + + it('render mobile menu button if chapters are present', () => { + const {queryByRole} = renderInEntry( + , + { + seed: { + chapters: [{}, {}] + } + } + ); + + expect(queryByRole('button', {name: 'Open mobile menu'})).not.toBeNull(); + }); + + it('toggles class to show and hide mobile menu', async () => { + const {getByRole} = renderInEntry( + , + { + 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 = () =>
Mobile menu
; + const {getByRole} = renderInEntry( + , + { + 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 = () =>
Mobile menu
; + const {queryByRole} = renderInEntry( + + ); + + expect(queryByRole('button', {name: 'Open mobile menu'})).not.toBeNull(); + }); + + it('passes open prop to custom mobile menu component', async () => { + const MobileMenu = ({open}) =>
Mobile menu {open ? 'open' : 'closed'}
; + const {queryByText, getByRole} = renderInEntry( + + ); + + 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}) => ( +
+
Mobile menu {open ? 'open' : 'closed'}
+ ; +
+ ); + const {queryByText, getByRole} = renderInEntry( + + ); + + 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}) =>
{configuration.mobileMenuTitle}
; + const {queryByText} = renderInEntry( + + ); + + expect(queryByText('Some title')).not.toBeNull(); + }); }); diff --git a/entry_types/scrolled/package/src/widgets/defaultNavigation/DefaultNavigation.js b/entry_types/scrolled/package/src/widgets/defaultNavigation/DefaultNavigation.js index 2e4ce670e..63345f48b 100644 --- a/entry_types/scrolled/package/src/widgets/defaultNavigation/DefaultNavigation.js +++ b/entry_types/scrolled/package/src/widgets/defaultNavigation/DefaultNavigation.js @@ -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); @@ -104,8 +104,8 @@ export function DefaultNavigation({configuration}) { return ( -