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
84 changes: 53 additions & 31 deletions entry_types/scrolled/package/spec/frontend/Tooltip-spec.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import React from 'react'
import {render, fireEvent, waitFor} from '@testing-library/react'
import {render, waitFor} from '@testing-library/react'
import '@testing-library/jest-dom/extend-expect'
import userEvent from '@testing-library/user-event';

import {Tooltip} from 'frontend/Tooltip';

describe('Tooltip', () => {
it('renders trigger', () => {
const {getByTestId} = render(
<Tooltip name="test">
{(buttonProps) => <button data-testid="trigger" {...buttonProps}>trigger</button>}
{(triggerProps) => <button data-testid="trigger" {...triggerProps}>trigger</button>}
</Tooltip>
);
expect(getByTestId('trigger')).toBeDefined();
Expand All @@ -23,143 +24,164 @@ describe('Tooltip', () => {
expect(getByTestId('content')).toBeDefined();
});

it('opens tooltip when button is clicked', () => {
it('opens tooltip when button is clicked', async () => {
const {getByTestId} = render(
<Tooltip name="test" content={<div data-testid="content">content</div>}>
{(buttonProps) => <button data-testid="trigger" {...buttonProps}>trigger</button>}
{(triggerProps) => <button data-testid="trigger" {...triggerProps}>trigger</button>}
</Tooltip>
);

const user = userEvent.setup();
const button = getByTestId('trigger');

expect(button.getAttribute('aria-expanded')).toBe('false');

fireEvent.click(button);
await user.click(button);

expect(button.getAttribute('aria-expanded')).toBe('true');
});

it('closes tooltip when button is clicked again', () => {
it('closes tooltip when button is clicked again', async () => {
const {getByTestId} = render(
<Tooltip name="test" content={<div data-testid="content">content</div>}>
{(buttonProps) => <button data-testid="trigger" {...buttonProps}>trigger</button>}
{(triggerProps) => <button data-testid="trigger" {...triggerProps}>trigger</button>}
</Tooltip>
);

const user = userEvent.setup();
const button = getByTestId('trigger');

fireEvent.click(button);
await user.click(button);
expect(button.getAttribute('aria-expanded')).toBe('true');

fireEvent.click(button);
await user.click(button);
expect(button.getAttribute('aria-expanded')).toBe('false');
});

it('closes tooltip when ESC key is pressed', () => {
it('closes tooltip when ESC key is pressed', async () => {
const {getByTestId} = render(
<Tooltip name="test" content={<div data-testid="content">content</div>}>
{(buttonProps) => <button data-testid="trigger" {...buttonProps}>trigger</button>}
{(triggerProps) => <button data-testid="trigger" {...triggerProps}>trigger</button>}
</Tooltip>
);

const user = userEvent.setup();
const button = getByTestId('trigger');

fireEvent.click(button);
await user.click(button);
expect(button.getAttribute('aria-expanded')).toBe('true');

fireEvent.keyDown(button, {key: 'Escape'});
await user.keyboard('{Escape}');
expect(button.getAttribute('aria-expanded')).toBe('false');
});

it('returns focus to button when ESC key is pressed', async () => {
const {getByTestId} = render(
<Tooltip name="test" content={<button data-testid="content-button">content button</button>}>
{(buttonProps) => <button data-testid="trigger" {...buttonProps}>trigger</button>}
{(triggerProps) => <button data-testid="trigger" {...triggerProps}>trigger</button>}
</Tooltip>
);

const user = userEvent.setup();
const button = getByTestId('trigger');
const contentButton = getByTestId('content-button');

fireEvent.click(button);
await user.click(button);
contentButton.focus();

expect(document.activeElement).toBe(contentButton);

fireEvent.keyDown(button, {key: 'Escape'});
await user.keyboard('{Escape}');

await waitFor(() => {
expect(document.activeElement).toBe(button);
});
});

it('sets aria-expanded and aria-controls attributes', () => {
it('sets aria-expanded and aria-controls attributes', async () => {
const {getByTestId} = render(
<Tooltip name="test" content={<div data-testid="content">content</div>}>
{(buttonProps) => <button data-testid="trigger" {...buttonProps}>trigger</button>}
{(triggerProps) => <button data-testid="trigger" {...triggerProps}>trigger</button>}
</Tooltip>
);

const user = userEvent.setup();
const button = getByTestId('trigger');

expect(button.getAttribute('aria-expanded')).toBe('false');
expect(button.getAttribute('aria-controls')).toBe('tooltip-test');

fireEvent.click(button);
await user.click(button);

expect(button.getAttribute('aria-expanded')).toBe('true');
expect(button.getAttribute('aria-controls')).toBe('tooltip-test');
});

it('does not toggle on click when openOnHover is true', () => {
it('does not toggle on click when openOnHover is true', async () => {
const {getByTestId} = render(
<Tooltip name="test" openOnHover content={<div data-testid="content">content</div>}>
{(buttonProps) => <button data-testid="trigger" {...buttonProps}>trigger</button>}
{(triggerProps) => <button data-testid="trigger" {...triggerProps}>trigger</button>}
</Tooltip>
);

const user = userEvent.setup();
const button = getByTestId('trigger');

expect(button.getAttribute('aria-expanded')).toBeNull();

fireEvent.click(button);
await user.click(button);

expect(button.getAttribute('aria-expanded')).toBeNull();
});

it('does not toggle on click when fixed is true', () => {
it('sets aria-describedby when openOnHover is true', () => {
const {getByTestId} = render(
<Tooltip name="test" openOnHover content={<div data-testid="content">content</div>}>
{(triggerProps) => <button data-testid="trigger" {...triggerProps}>trigger</button>}
</Tooltip>
);

const button = getByTestId('trigger');

expect(button.getAttribute('aria-describedby')).toBe('tooltip-test');
});

it('does not toggle on click when fixed is true', async () => {
const {getByTestId} = render(
<Tooltip name="test" fixed content={<div data-testid="content">content</div>}>
{(buttonProps) => <button data-testid="trigger" {...buttonProps}>trigger</button>}
{(triggerProps) => <button data-testid="trigger" {...triggerProps}>trigger</button>}
</Tooltip>
);

const user = userEvent.setup();
const button = getByTestId('trigger');

expect(button.getAttribute('aria-expanded')).toBeNull();

fireEvent.click(button);
await user.click(button);

expect(button.getAttribute('aria-expanded')).toBeNull();
});

it('closes tooltip when focus leaves the container', () => {
it('closes tooltip when focus leaves the container', async () => {
const {getByTestId} = render(
<>
<Tooltip name="test" content={<div data-testid="content">content</div>}>
{(buttonProps) => <button data-testid="trigger" {...buttonProps}>trigger</button>}
<Tooltip name="test" content={<button data-testid="content-button">content button</button>}>
{(triggerProps) => <button data-testid="trigger" {...triggerProps}>trigger</button>}
</Tooltip>
<button data-testid="outside">outside</button>
</>
);

const user = userEvent.setup();
const button = getByTestId('trigger');
const outsideButton = getByTestId('outside');
const contentButton = getByTestId('content-button');

fireEvent.click(button);
await user.click(button);
expect(button.getAttribute('aria-expanded')).toBe('true');

fireEvent.blur(button, {relatedTarget: outsideButton});
contentButton.focus();
await user.keyboard('{Tab}');

expect(button.getAttribute('aria-expanded')).toBe('false');
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,28 @@ describe('DefaultNavigation - Chapters', () => {
expect(queryByRole('link', {name: 'Second chapter'})).not.toBeNull();
expect(queryByRole('link', {name: 'Hidden chapter'})).toBeNull();
});

it('sets aria-current on current chapter link', () => {
const {getByRole} = renderInEntry(
<DefaultNavigation configuration={{}} />,
{
seed: {
chapters: [
{id: 1, configuration: {title: 'First chapter'}},
{id: 2, configuration: {title: 'Second chapter'}}
],
sections: [
{chapterId: 1},
{chapterId: 2}
]
}
}
);

const firstChapterLink = getByRole('link', {name: 'First chapter'});
const secondChapterLink = getByRole('link', {name: 'Second chapter'});

expect(firstChapterLink.getAttribute('aria-current')).toBe('location');
expect(secondChapterLink.getAttribute('aria-current')).toBeNull();
});
});
30 changes: 26 additions & 4 deletions entry_types/scrolled/package/src/frontend/Tooltip.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {useState, useRef} from 'react'
import React, {useState, useRef, useEffect} from 'react'
import classNames from 'classnames';

import styles from './Tooltip.module.css'
Expand Down Expand Up @@ -33,18 +33,40 @@ export function Tooltip({
};

const handleBlur = (event) => {
if (isOpen && !containerRef.current?.contains(event.relatedTarget)) {
if (isOpen &&
event.relatedTarget &&
!containerRef.current?.contains(event.relatedTarget)) {
setIsOpen(false);
}
};

const isControlled = !openOnHover && !fixed;

const buttonProps = isControlled ? {
useEffect(() => {
if (!isControlled || !isOpen) {
return;
}

const handleDocumentClick = (event) => {
if (!containerRef.current?.contains(event.target)) {
setIsOpen(false);
}
};

document.addEventListener('click', handleDocumentClick);

return () => {
document.removeEventListener('click', handleDocumentClick);
};
}, [isControlled, isOpen]);

const triggerProps = isControlled ? {
onClick: handleClick,
ref: buttonRef,
'aria-expanded': isOpen,
'aria-controls': tooltipId
} : openOnHover ? {
'aria-describedby': tooltipId
} : {};

return (
Expand All @@ -55,7 +77,7 @@ export function Tooltip({
})}
onKeyDown={isControlled ? handleKeyDown : undefined}
onBlur={isControlled ? handleBlur : undefined}>
{typeof children === 'function' ? children(buttonProps) : children}
{typeof children === 'function' ? children(triggerProps) : children}
<Bubble className={bubbleClassName}
highlight={highlight}
arrowPos={arrowPos}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ const {isBlank, presence} = utils;
export function ChapterLink(props) {
const {t} = useI18n();

const item = (
const renderLink = (triggerProps = {}) => (
<div>
<a className={classNames(styles.chapterLink, {[styles.chapterLinkActive]: props.active})}
<a {...triggerProps}
className={classNames(styles.chapterLink, {[styles.chapterLinkActive]: props.active})}
href={`#${props.chapterSlug}`}
onClick={() => props.handleMenuClick(props.chapterLinkId)}>
onClick={() => props.handleMenuClick(props.chapterLinkId)}
aria-current={props.active ? 'location' : undefined}>
{presence(props.title) || t('pageflow_scrolled.public.navigation.chapter', {number: props.chapterIndex})}
</a>
{!isBlank(props.summary) && <p className={classNames(styles.summary, styles.inlineSummary) }
Expand All @@ -26,7 +28,7 @@ export function ChapterLink(props) {
);

if (isBlank(props.summary)) {
return item;
return renderLink();
}

const content = (
Expand All @@ -35,7 +37,7 @@ export function ChapterLink(props) {

return (
<Tooltip name={`chapter-${props.chapterLinkId}`} content={content} openOnHover={true} highlight={true} bubbleClassName={styles.tooltipBubble}>
{item}
{renderLink}
</Tooltip>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,12 @@ export function DefaultNavigation({
onFocus={() => setNavExpanded(true)}>
<WidgetSelectionRect>
<div className={styles.navigationBarContentWrapper}>
<SkipLinks />

{(hasChapters || hasMenu) && <HamburgerIcon onClick={handleBurgerMenuClick}
menuOpen={menuOpen}
visibleOnDesktop={hasDesktopMenu}/>}

<SkipLinks />
<Logo {...logo} />

{renderNav()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ export function LegalInfoMenu(props) {
horizontalOffset={props.tooltipOffset - 30}
arrowPos={120 - props.tooltipOffset}
content={content}>
{(buttonProps) => (
<button {...buttonProps}
{(triggerProps) => (
<button {...triggerProps}
className={classNames(headerStyles.contextIcon)}
title={t('pageflow_scrolled.public.navigation.legal_info')}>
<ThemeIcon name="information" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ export function SharingMenu({shareProviders}) {
horizontalOffset={-70}
arrowPos={160}
content={renderShareLinks(shareProviders)}>
{(buttonProps) => (
<button {...buttonProps}
{(triggerProps) => (
<button {...triggerProps}
className={classNames(headerStyles.contextIcon)}
title={t('pageflow_scrolled.public.navigation.share')}>
<ThemeIcon name="share" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ export function TranslationsMenu({tooltipOffset = 0}) {
horizontalOffset={tooltipOffset - 30}
arrowPos={120 - tooltipOffset}
content={content}>
{(buttonProps) => (
<button {...buttonProps}
{(triggerProps) => (
<button {...triggerProps}
className={classNames(headerStyles.contextIcon)}
title={t('pageflow_scrolled.public.navigation.language')}>
<ThemeIcon name="world" />
Expand Down
Loading