- Tooltips on hover and keyboard focus across the app: icon-only buttons show
their label, existing titles show in the app's style without the browser's
delay, and the reader's main buttons show shortcut hints ("Search (/)").
Touch screens are unaffected.
- Reader: cross-references (under verses and in the Cross-refs tab) open the
passage's text in the side panel without leaving the chapter; "Open chapter"
goes there when wanted
- Reader: the verse panel now has a Highlight row of colours for one-tap
whole-verse highlighting (tap again to remove, Clear for all, with Undo);
selecting words still highlights just those
- Fix: a cross-reference preview was cleared when React ran the panel's
effect twice (development builds)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
89 lines
3.2 KiB
React
89 lines
3.2 KiB
React
import { render, screen, fireEvent, act } from '@testing-library/react';
|
||
import { vi, beforeEach, afterEach, test, expect } from 'vitest';
|
||
import Tooltips, { tipFor } from './Tooltips.jsx';
|
||
|
||
beforeEach(() => vi.useFakeTimers());
|
||
afterEach(() => vi.useRealTimers());
|
||
|
||
const over = (el) => act(() => { fireEvent.pointerOver(el); vi.advanceTimersByTime(600); });
|
||
|
||
function setup() {
|
||
render(
|
||
<>
|
||
<button aria-label="Search" data-tip="Search (/)">🔍</button>
|
||
<button aria-label="Close">×</button>
|
||
<button title="Out a level (Shift+Tab)">←</button>
|
||
<button aria-label="Save changes">Save changes</button>
|
||
<button disabled aria-label="Nothing">·</button>
|
||
<Tooltips theme="paper" />
|
||
</>,
|
||
);
|
||
}
|
||
|
||
test('shows a data-tip after a short pause, and hides when the pointer leaves', () => {
|
||
setup();
|
||
const btn = screen.getByLabelText('Search');
|
||
over(btn);
|
||
expect(screen.getByRole('tooltip')).toHaveTextContent('Search (/)');
|
||
expect(btn).toHaveAttribute('aria-describedby', 'app-tooltip');
|
||
act(() => { fireEvent.pointerOut(btn); });
|
||
expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();
|
||
expect(btn).not.toHaveAttribute('aria-describedby');
|
||
});
|
||
|
||
test('does not appear before the pause', () => {
|
||
setup();
|
||
act(() => { fireEvent.pointerOver(screen.getByLabelText('Search')); vi.advanceTimersByTime(100); });
|
||
expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();
|
||
});
|
||
|
||
test('an icon-only button shows its label', () => {
|
||
setup();
|
||
over(screen.getByLabelText('Close'));
|
||
expect(screen.getByRole('tooltip')).toHaveTextContent('Close');
|
||
});
|
||
|
||
test('a title is shown in the app style and the browser\'s own tooltip is turned off', () => {
|
||
setup();
|
||
const btn = screen.getByText('←');
|
||
over(btn);
|
||
expect(screen.getByRole('tooltip')).toHaveTextContent('Out a level (Shift+Tab)');
|
||
expect(btn).not.toHaveAttribute('title');
|
||
expect(btn).toHaveAttribute('aria-label', 'Out a level (Shift+Tab)'); // a screen reader still gets its name
|
||
});
|
||
|
||
test('a button that already says what it is gets no tooltip, and neither does a disabled one', () => {
|
||
setup();
|
||
over(screen.getByText('Save changes'));
|
||
expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();
|
||
over(screen.getByLabelText('Nothing'));
|
||
expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();
|
||
});
|
||
|
||
test('clicking, scrolling or Escape dismisses it', () => {
|
||
setup();
|
||
const btn = screen.getByLabelText('Search');
|
||
over(btn);
|
||
act(() => { fireEvent.pointerDown(btn); });
|
||
expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();
|
||
over(btn);
|
||
act(() => { fireEvent.keyDown(document, { key: 'Escape' }); });
|
||
expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();
|
||
over(btn);
|
||
act(() => { fireEvent.scroll(window); });
|
||
expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();
|
||
});
|
||
|
||
test('nothing shows on a device without hover (touch)', () => {
|
||
vi.stubGlobal('matchMedia', () => ({ matches: false }));
|
||
setup();
|
||
over(screen.getByLabelText('Search'));
|
||
expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();
|
||
vi.unstubAllGlobals();
|
||
});
|
||
|
||
test('tipFor ignores things that are not elements', () => {
|
||
expect(tipFor(null)).toBe('');
|
||
expect(tipFor(document.createTextNode('x'))).toBe('');
|
||
});
|