Hover tooltips, clickable cross-references, easier highlighting
- 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>
This commit is contained in:
1 parent
bb80623c45
commit
7f0256be06
9 files changed
+394
-28
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { render, screen, fireEvent, within, waitFor } from '@testing-library/react';
|
||||
import { StrictMode } from 'react';
|
||||
import { vi, test, expect } from 'vitest';
|
||||
|
||||
const noop = () => {};
|
||||
@@ -58,6 +59,64 @@ test('the compare button next to Aa opens the comparison sheet, and choosing a B
|
||||
expect(ctx.setReaderParallel).toHaveBeenCalledWith('NET');
|
||||
});
|
||||
|
||||
test('a cross-reference under a verse opens the side panel with that passage, without leaving the chapter', async () => {
|
||||
const saved = { ...ctx };
|
||||
Object.assign(ctx, {
|
||||
readerShowCrossRefs: true, readerSelectedVerse: 1,
|
||||
readerCrossRefs: { 1: [{ book: 'ROM', chapter: 5, verse: 8, endVerse: 9 }] },
|
||||
jumpToReaderVerse: vi.fn(),
|
||||
});
|
||||
vi.stubGlobal('fetch', vi.fn((url) => Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve(String(url).includes('/api/bible/')
|
||||
? { verses: [{ number: 7, text: 'Seven.' }, { number: 8, text: 'But God shows His love for us.' }, { number: 9, text: 'Much more shall we be saved.' }, { number: 10, text: 'Ten.' }] }
|
||||
: { chapter: { content: [] } }),
|
||||
})));
|
||||
render(<StrictMode><ReaderPage /></StrictMode>); // dev builds run effects twice; the preview must survive that
|
||||
const chip = within(screen.getByRole('article')).getByRole('button', { name: 'Romans 5:8-9' });
|
||||
fireEvent.click(chip);
|
||||
const card = await screen.findByRole('region', { name: /cross-reference romans 5:8-9/i });
|
||||
await waitFor(() => expect(within(card).getByText(/But God shows His love/)).toBeTruthy());
|
||||
expect(within(card).getByText(/Much more shall we be saved/)).toBeTruthy();
|
||||
expect(within(card).queryByText('Seven.')).toBeNull(); // only the verses in the reference
|
||||
expect(ctx.setReaderSelectedVerse).not.toHaveBeenCalledWith(null); // the click didn't toggle the verse off
|
||||
fireEvent.click(within(card).getByRole('button', { name: /open chapter/i }));
|
||||
expect(ctx.jumpToReaderVerse).toHaveBeenCalledWith('ROM', 5, 8); // only now does it leave the chapter
|
||||
fireEvent.click(within(card).getByRole('button', { name: /close cross-reference/i }));
|
||||
expect(screen.queryByRole('region', { name: /cross-reference romans/i })).toBeNull();
|
||||
Object.assign(ctx, saved);
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
test('the verse panel has one-tap highlight colours for the whole verse', () => {
|
||||
const saved = { ...ctx };
|
||||
Object.assign(ctx, { readerSelectedVerse: 2, addTextHighlight: vi.fn(), removeTextHighlight: vi.fn(), restoreTextHighlight: vi.fn(), readerTextHighlights: [] });
|
||||
render(<ReaderPage />);
|
||||
const row = screen.getByRole('group', { name: /highlight this verse/i });
|
||||
fireEvent.click(within(row).getByRole('button', { name: /highlight verse green/i }));
|
||||
expect(ctx.addTextHighlight).toHaveBeenCalledWith(expect.objectContaining({
|
||||
book: 'JHN', chapter: 3, verse: 2, startOffset: 0, endOffset: 'This man came to Jesus by night.'.length, color: '#bbf7d0', text: 'This man came to Jesus by night.',
|
||||
}));
|
||||
Object.assign(ctx, saved);
|
||||
});
|
||||
|
||||
test('tapping a verse colour again takes it off, and Clear removes them all (with Undo)', () => {
|
||||
const saved = { ...ctx };
|
||||
const text = 'This man came to Jesus by night.';
|
||||
const hl = (id, color, start = 0, end = text.length) => ({ id, book: 'JHN', chapter: 3, verse: 2, startOffset: start, endOffset: end, color });
|
||||
Object.assign(ctx, { readerSelectedVerse: 2, addTextHighlight: vi.fn(), removeTextHighlight: vi.fn(), restoreTextHighlight: vi.fn(), readerTextHighlights: [hl('a', '#bbf7d0'), hl('b', '#fef08a', 5, 8)] });
|
||||
render(<ReaderPage />);
|
||||
const row = screen.getByRole('group', { name: /highlight this verse/i });
|
||||
expect(within(row).getByRole('button', { name: /highlight verse green/i })).toHaveAttribute('aria-pressed', 'true');
|
||||
expect(within(row).getByRole('button', { name: /highlight verse yellow/i })).toHaveAttribute('aria-pressed', 'false'); // a part-verse highlight isn't "the verse"
|
||||
fireEvent.click(within(row).getByRole('button', { name: /highlight verse green/i }));
|
||||
expect(ctx.removeTextHighlight).toHaveBeenCalledWith('a');
|
||||
expect(ctx.addTextHighlight).not.toHaveBeenCalled();
|
||||
fireEvent.click(within(row).getByRole('button', { name: /^clear$/i }));
|
||||
expect(ctx.removeTextHighlight).toHaveBeenCalledWith('b');
|
||||
Object.assign(ctx, saved);
|
||||
});
|
||||
|
||||
test('tapping a verse selects it', () => {
|
||||
render(<ReaderPage />);
|
||||
fireEvent.click(screen.getByText(/This man came to Jesus/));
|
||||
|
||||
Reference in new issue
Block a user