From 2e30e3090e77705725ed5eb51eced47954bbf08b Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Fri, 15 May 2026 09:23:15 -0400 Subject: [PATCH 1/4] Remove Scripture nav controls --- .../components/InterlinearizerLoader.test.tsx | 8 +-- .../components/ScriptureNavControls.test.tsx | 56 ----------------- src/components/InterlinearizerLoader.tsx | 16 +---- src/components/ScriptureNavControls.tsx | 63 ------------------- 4 files changed, 3 insertions(+), 140 deletions(-) delete mode 100644 src/__tests__/components/ScriptureNavControls.test.tsx delete mode 100644 src/components/ScriptureNavControls.tsx diff --git a/src/__tests__/components/InterlinearizerLoader.test.tsx b/src/__tests__/components/InterlinearizerLoader.test.tsx index 4e422961..ec9d7c47 100644 --- a/src/__tests__/components/InterlinearizerLoader.test.tsx +++ b/src/__tests__/components/InterlinearizerLoader.test.tsx @@ -36,11 +36,6 @@ jest.mock('../../components/ContinuousScrollToggle', () => ({ ), })); -jest.mock('../../components/ScriptureNavControls', () => ({ - __esModule: true, - default: () =>
, -})); - jest.mock('../../components/ContinuousView', () => ({ __esModule: true, default: () =>
, @@ -136,7 +131,7 @@ describe('InterlinearizerLoader', () => { jest.mocked(useLocalizedStrings).mockReturnValue([{}, false]); }); - it('renders Interlinearizer and the nav controls when book data is available', () => { + it('renders Interlinearizer when book data is available', () => { render( { />, ); - expect(screen.getByTestId('scripture-nav-controls')).toBeInTheDocument(); expect(screen.getByTestId('interlinearizer')).toBeInTheDocument(); }); diff --git a/src/__tests__/components/ScriptureNavControls.test.tsx b/src/__tests__/components/ScriptureNavControls.test.tsx deleted file mode 100644 index a7c2bd73..00000000 --- a/src/__tests__/components/ScriptureNavControls.test.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/** @file Unit tests for components/ScriptureNavControls.tsx. */ -/// -/// - -import type { SerializedVerseRef } from '@sillsdev/scripture'; -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { useLocalizedStrings, useRecentScriptureRefs } from '@papi/frontend/react'; -import ScriptureNavControls from '../../components/ScriptureNavControls'; - -const defaultScrRef: SerializedVerseRef = { book: 'GEN', chapterNum: 1, verseNum: 1 }; - -describe('ScriptureNavControls', () => { - beforeEach(() => { - jest.mocked(useLocalizedStrings).mockReturnValue([{}, false]); - jest.mocked(useRecentScriptureRefs).mockReturnValue({ - recentScriptureRefs: [], - addRecentScriptureRef: jest.fn(), - }); - }); - - it('shows the book chapter control', () => { - render( - {}} - scrollGroupId={undefined} - onChangeScrollGroupId={() => {}} - />, - ); - - expect(screen.getByTestId('book-chapter-control')).toBeInTheDocument(); - }); - - it('calls handleSubmit and addRecentScriptureRef when the verse picker submits', async () => { - const mockHandleSubmit = jest.fn(); - const mockAddRecentRef = jest.fn(); - jest.mocked(useRecentScriptureRefs).mockReturnValue({ - recentScriptureRefs: [], - addRecentScriptureRef: mockAddRecentRef, - }); - render( - {}} - />, - ); - - await userEvent.click(screen.getByRole('button', { name: /submit reference/i })); - - expect(mockHandleSubmit).toHaveBeenCalledWith(defaultScrRef); - expect(mockAddRecentRef).toHaveBeenCalledWith(defaultScrRef); - }); -}); diff --git a/src/components/InterlinearizerLoader.tsx b/src/components/InterlinearizerLoader.tsx index a3e8a1ec..6fbdd623 100644 --- a/src/components/InterlinearizerLoader.tsx +++ b/src/components/InterlinearizerLoader.tsx @@ -4,7 +4,6 @@ import { TabToolbar } from 'platform-bible-react'; import { useMemo } from 'react'; import ContinuousScrollToggle from './ContinuousScrollToggle'; import Interlinearizer from './Interlinearizer'; -import ScriptureNavControls from './ScriptureNavControls'; import useInterlinearizerBookData from '../hooks/useInterlinearizerBookData'; import useOptimisticBooleanSetting from '../hooks/useOptimisticBooleanSetting'; @@ -27,7 +26,7 @@ export default function InterlinearizerLoader({ projectId: string; useWebViewScrollGroupScrRef: UseWebViewScrollGroupScrRefHook; }>) { - const [scrRef, setScrRef, scrollGroupId, setScrollGroupId] = useWebViewScrollGroupScrRef(); + const [scrRef, setScrRef] = useWebViewScrollGroupScrRef(); const { isLoading: isSettingLoading, @@ -36,10 +35,7 @@ export default function InterlinearizerLoader({ } = useOptimisticBooleanSetting(projectId, 'interlinearizer.continuousScroll', true); const { book, chapterSegments, isLoading, bookError, tokenizeError } = useInterlinearizerBookData( - { - projectId, - scrRef, - }, + { projectId, scrRef }, ); const hasError = !!bookError || !!tokenizeError; @@ -50,14 +46,6 @@ export default function InterlinearizerLoader({ const toolbar = ( - } endAreaChildren={ & - Pick; - -/** - * Toolbar row combining a {@link BookChapterControl} for scripture navigation with a - * {@link ScrollGroupSelector} for Platform.Bible scroll-group linking. - * - * @param props - Component props - * @param props.scrRef - Current scripture reference displayed in the book/chapter picker. - * @param props.handleSubmit - Called when the user submits a new scripture reference. - * @param props.scrollGroupId - Currently active scroll group ID (or `undefined` for none). - * @param props.onChangeScrollGroupId - Called when the user selects a different scroll group. - * @returns A flex row containing the book/chapter control and the scroll group selector. - */ -export default function ScriptureNavControls({ - scrRef, - handleSubmit, - scrollGroupId, - onChangeScrollGroupId, -}: ScriptureNavControlsProps) { - const [localizedStrings] = useLocalizedStrings( - useMemo(() => [...BOOK_CHAPTER_CONTROL_STRING_KEYS], []), - ); - const { recentScriptureRefs: recentRefs, addRecentScriptureRef: onAddRecentRef } = - useRecentScriptureRefs(); - - return ( -
- - -
- ); -} From 81b1a89fbf26873cf3b39330ea9fd63d1b50ff9c Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Fri, 15 May 2026 09:31:54 -0400 Subject: [PATCH 2/4] Trim mock --- __mocks__/platform-bible-react.tsx | 61 +----------------------------- 1 file changed, 1 insertion(+), 60 deletions(-) diff --git a/__mocks__/platform-bible-react.tsx b/__mocks__/platform-bible-react.tsx index ba354760..710c78e3 100644 --- a/__mocks__/platform-bible-react.tsx +++ b/__mocks__/platform-bible-react.tsx @@ -1,22 +1,11 @@ /** * @file Jest mock for platform-bible-react. The real package ships ESM which Jest cannot parse * without extra transform configuration. This stub provides the subset used by extension - * components: `BookChapterControl`, `BOOK_CHAPTER_CONTROL_STRING_KEYS`, `TabToolbar`, and - * `ScrollGroupSelector`. + * components: `TabToolbar`, `Switch`, and `Label`. */ import type { ReactElement, ReactNode } from 'react'; -interface SerializedVerseRef { - book: string; - chapterNum: number; - verseNum: number; - verse?: string; - versificationStr?: string; -} - -export const BOOK_CHAPTER_CONTROL_STRING_KEYS: string[] = []; - export function TabToolbar({ startAreaChildren, endAreaChildren, @@ -35,54 +24,6 @@ export function TabToolbar({ ); } -export function ScrollGroupSelector({ - availableScrollGroupIds, - scrollGroupId, - onChangeScrollGroupId, -}: Readonly<{ - availableScrollGroupIds?: (number | undefined)[]; - scrollGroupId?: number; - onChangeScrollGroupId?: (id: number | undefined) => void; -}>): ReactElement { - return ( - - ); -} - -export function BookChapterControl({ - scrRef, - handleSubmit, - onAddRecentSearch, -}: Readonly<{ - scrRef: SerializedVerseRef; - handleSubmit: (ref: SerializedVerseRef) => void; - className?: string; - localizedStrings?: Record; - recentSearches?: SerializedVerseRef[]; - onAddRecentSearch?: (scrRef: SerializedVerseRef) => void; - id?: string; -}>): ReactElement { - return ( -
- {scrRef.book} {scrRef.chapterNum}:{scrRef.verseNum} - -
- ); -} - export function Switch({ checked, disabled, From 49d63a55a44acf702f3e05230f263ecbf261092f Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Fri, 15 May 2026 14:05:16 -0400 Subject: [PATCH 3/4] Revert "Trim mock" and "Remove Scripture nav controls" This reverts commits 81b1a89 and 2e30e30. Co-Authored-By: Claude Sonnet 4.6 --- __mocks__/platform-bible-react.tsx | 61 +++++++++++++++++- .../components/InterlinearizerLoader.test.tsx | 8 ++- .../components/ScriptureNavControls.test.tsx | 56 +++++++++++++++++ src/components/InterlinearizerLoader.tsx | 16 ++++- src/components/ScriptureNavControls.tsx | 63 +++++++++++++++++++ 5 files changed, 200 insertions(+), 4 deletions(-) create mode 100644 src/__tests__/components/ScriptureNavControls.test.tsx create mode 100644 src/components/ScriptureNavControls.tsx diff --git a/__mocks__/platform-bible-react.tsx b/__mocks__/platform-bible-react.tsx index 710c78e3..ba354760 100644 --- a/__mocks__/platform-bible-react.tsx +++ b/__mocks__/platform-bible-react.tsx @@ -1,11 +1,22 @@ /** * @file Jest mock for platform-bible-react. The real package ships ESM which Jest cannot parse * without extra transform configuration. This stub provides the subset used by extension - * components: `TabToolbar`, `Switch`, and `Label`. + * components: `BookChapterControl`, `BOOK_CHAPTER_CONTROL_STRING_KEYS`, `TabToolbar`, and + * `ScrollGroupSelector`. */ import type { ReactElement, ReactNode } from 'react'; +interface SerializedVerseRef { + book: string; + chapterNum: number; + verseNum: number; + verse?: string; + versificationStr?: string; +} + +export const BOOK_CHAPTER_CONTROL_STRING_KEYS: string[] = []; + export function TabToolbar({ startAreaChildren, endAreaChildren, @@ -24,6 +35,54 @@ export function TabToolbar({ ); } +export function ScrollGroupSelector({ + availableScrollGroupIds, + scrollGroupId, + onChangeScrollGroupId, +}: Readonly<{ + availableScrollGroupIds?: (number | undefined)[]; + scrollGroupId?: number; + onChangeScrollGroupId?: (id: number | undefined) => void; +}>): ReactElement { + return ( + + ); +} + +export function BookChapterControl({ + scrRef, + handleSubmit, + onAddRecentSearch, +}: Readonly<{ + scrRef: SerializedVerseRef; + handleSubmit: (ref: SerializedVerseRef) => void; + className?: string; + localizedStrings?: Record; + recentSearches?: SerializedVerseRef[]; + onAddRecentSearch?: (scrRef: SerializedVerseRef) => void; + id?: string; +}>): ReactElement { + return ( +
+ {scrRef.book} {scrRef.chapterNum}:{scrRef.verseNum} + +
+ ); +} + export function Switch({ checked, disabled, diff --git a/src/__tests__/components/InterlinearizerLoader.test.tsx b/src/__tests__/components/InterlinearizerLoader.test.tsx index ec9d7c47..4e422961 100644 --- a/src/__tests__/components/InterlinearizerLoader.test.tsx +++ b/src/__tests__/components/InterlinearizerLoader.test.tsx @@ -36,6 +36,11 @@ jest.mock('../../components/ContinuousScrollToggle', () => ({ ), })); +jest.mock('../../components/ScriptureNavControls', () => ({ + __esModule: true, + default: () =>
, +})); + jest.mock('../../components/ContinuousView', () => ({ __esModule: true, default: () =>
, @@ -131,7 +136,7 @@ describe('InterlinearizerLoader', () => { jest.mocked(useLocalizedStrings).mockReturnValue([{}, false]); }); - it('renders Interlinearizer when book data is available', () => { + it('renders Interlinearizer and the nav controls when book data is available', () => { render( { />, ); + expect(screen.getByTestId('scripture-nav-controls')).toBeInTheDocument(); expect(screen.getByTestId('interlinearizer')).toBeInTheDocument(); }); diff --git a/src/__tests__/components/ScriptureNavControls.test.tsx b/src/__tests__/components/ScriptureNavControls.test.tsx new file mode 100644 index 00000000..a7c2bd73 --- /dev/null +++ b/src/__tests__/components/ScriptureNavControls.test.tsx @@ -0,0 +1,56 @@ +/** @file Unit tests for components/ScriptureNavControls.tsx. */ +/// +/// + +import type { SerializedVerseRef } from '@sillsdev/scripture'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { useLocalizedStrings, useRecentScriptureRefs } from '@papi/frontend/react'; +import ScriptureNavControls from '../../components/ScriptureNavControls'; + +const defaultScrRef: SerializedVerseRef = { book: 'GEN', chapterNum: 1, verseNum: 1 }; + +describe('ScriptureNavControls', () => { + beforeEach(() => { + jest.mocked(useLocalizedStrings).mockReturnValue([{}, false]); + jest.mocked(useRecentScriptureRefs).mockReturnValue({ + recentScriptureRefs: [], + addRecentScriptureRef: jest.fn(), + }); + }); + + it('shows the book chapter control', () => { + render( + {}} + scrollGroupId={undefined} + onChangeScrollGroupId={() => {}} + />, + ); + + expect(screen.getByTestId('book-chapter-control')).toBeInTheDocument(); + }); + + it('calls handleSubmit and addRecentScriptureRef when the verse picker submits', async () => { + const mockHandleSubmit = jest.fn(); + const mockAddRecentRef = jest.fn(); + jest.mocked(useRecentScriptureRefs).mockReturnValue({ + recentScriptureRefs: [], + addRecentScriptureRef: mockAddRecentRef, + }); + render( + {}} + />, + ); + + await userEvent.click(screen.getByRole('button', { name: /submit reference/i })); + + expect(mockHandleSubmit).toHaveBeenCalledWith(defaultScrRef); + expect(mockAddRecentRef).toHaveBeenCalledWith(defaultScrRef); + }); +}); diff --git a/src/components/InterlinearizerLoader.tsx b/src/components/InterlinearizerLoader.tsx index 6fbdd623..a3e8a1ec 100644 --- a/src/components/InterlinearizerLoader.tsx +++ b/src/components/InterlinearizerLoader.tsx @@ -4,6 +4,7 @@ import { TabToolbar } from 'platform-bible-react'; import { useMemo } from 'react'; import ContinuousScrollToggle from './ContinuousScrollToggle'; import Interlinearizer from './Interlinearizer'; +import ScriptureNavControls from './ScriptureNavControls'; import useInterlinearizerBookData from '../hooks/useInterlinearizerBookData'; import useOptimisticBooleanSetting from '../hooks/useOptimisticBooleanSetting'; @@ -26,7 +27,7 @@ export default function InterlinearizerLoader({ projectId: string; useWebViewScrollGroupScrRef: UseWebViewScrollGroupScrRefHook; }>) { - const [scrRef, setScrRef] = useWebViewScrollGroupScrRef(); + const [scrRef, setScrRef, scrollGroupId, setScrollGroupId] = useWebViewScrollGroupScrRef(); const { isLoading: isSettingLoading, @@ -35,7 +36,10 @@ export default function InterlinearizerLoader({ } = useOptimisticBooleanSetting(projectId, 'interlinearizer.continuousScroll', true); const { book, chapterSegments, isLoading, bookError, tokenizeError } = useInterlinearizerBookData( - { projectId, scrRef }, + { + projectId, + scrRef, + }, ); const hasError = !!bookError || !!tokenizeError; @@ -46,6 +50,14 @@ export default function InterlinearizerLoader({ const toolbar = ( + } endAreaChildren={ & + Pick; + +/** + * Toolbar row combining a {@link BookChapterControl} for scripture navigation with a + * {@link ScrollGroupSelector} for Platform.Bible scroll-group linking. + * + * @param props - Component props + * @param props.scrRef - Current scripture reference displayed in the book/chapter picker. + * @param props.handleSubmit - Called when the user submits a new scripture reference. + * @param props.scrollGroupId - Currently active scroll group ID (or `undefined` for none). + * @param props.onChangeScrollGroupId - Called when the user selects a different scroll group. + * @returns A flex row containing the book/chapter control and the scroll group selector. + */ +export default function ScriptureNavControls({ + scrRef, + handleSubmit, + scrollGroupId, + onChangeScrollGroupId, +}: ScriptureNavControlsProps) { + const [localizedStrings] = useLocalizedStrings( + useMemo(() => [...BOOK_CHAPTER_CONTROL_STRING_KEYS], []), + ); + const { recentScriptureRefs: recentRefs, addRecentScriptureRef: onAddRecentRef } = + useRecentScriptureRefs(); + + return ( +
+ + +
+ ); +} From 062863b77274ce8f9829e49ae723d6e8aa2971dd Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Fri, 15 May 2026 14:21:55 -0400 Subject: [PATCH 4/4] Hide Scripture nav controls in simple interface mode Use `useSetting('platform.interfaceMode', 'simple')` in InterlinearizerLoader and render ScriptureNavControls only when the value is 'power'. Co-Authored-By: Claude Sonnet 4.6 --- __mocks__/papi-frontend-react.ts | 18 ++++++++++++++++++ __mocks__/platform-bible-react.tsx | 4 +--- .../components/InterlinearizerLoader.test.tsx | 18 ++++++++++++++++-- src/components/InterlinearizerLoader.tsx | 18 +++++++++++------- 4 files changed, 46 insertions(+), 12 deletions(-) diff --git a/__mocks__/papi-frontend-react.ts b/__mocks__/papi-frontend-react.ts index e7f99bb6..8aea1ce2 100644 --- a/__mocks__/papi-frontend-react.ts +++ b/__mocks__/papi-frontend-react.ts @@ -62,6 +62,23 @@ const useLocalizedStrings = jest.fn().mockImplementation((keys: string[]) => [ false, ]); +/** + * Mock for `useSetting`. Returns `[defaultState, setSetting, resetSetting, false]`, passing + * `defaultState` through unchanged so callers receive a predictable initial value. + * + * @param _key - Ignored setting key. + * @param defaultState - Value surfaced as the current setting state. + * @returns Tuple of `[defaultState, jest.fn(), jest.fn(), false]`. + */ +const useSetting = jest + .fn() + .mockImplementation((_key: string, defaultState: unknown) => [ + defaultState, + jest.fn(), + jest.fn(), + false, + ]); + /** * Mock for `useRecentScriptureRefs`. Returns an empty history and a no-op `addRecentScriptureRef` * so components that display recent references render without errors. @@ -76,6 +93,7 @@ module.exports = { __esModule: true, useProjectData, useProjectSetting, + useSetting, useLocalizedStrings, useRecentScriptureRefs, }; diff --git a/__mocks__/platform-bible-react.tsx b/__mocks__/platform-bible-react.tsx index ba354760..3d1bd3e5 100644 --- a/__mocks__/platform-bible-react.tsx +++ b/__mocks__/platform-bible-react.tsx @@ -1,8 +1,6 @@ /** * @file Jest mock for platform-bible-react. The real package ships ESM which Jest cannot parse - * without extra transform configuration. This stub provides the subset used by extension - * components: `BookChapterControl`, `BOOK_CHAPTER_CONTROL_STRING_KEYS`, `TabToolbar`, and - * `ScrollGroupSelector`. + * without extra transform configuration. This stub provides the subset used by the extension. */ import type { ReactElement, ReactNode } from 'react'; diff --git a/src/__tests__/components/InterlinearizerLoader.test.tsx b/src/__tests__/components/InterlinearizerLoader.test.tsx index 4e422961..5be70998 100644 --- a/src/__tests__/components/InterlinearizerLoader.test.tsx +++ b/src/__tests__/components/InterlinearizerLoader.test.tsx @@ -2,7 +2,7 @@ /// /// -import { useLocalizedStrings } from '@papi/frontend/react'; +import { useLocalizedStrings, useSetting } from '@papi/frontend/react'; import type { SerializedVerseRef } from '@sillsdev/scripture'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; @@ -134,9 +134,11 @@ describe('InterlinearizerLoader', () => { mockBookData(); mockOptimisticSetting(); jest.mocked(useLocalizedStrings).mockReturnValue([{}, false]); + jest.mocked(useSetting).mockReturnValue(['simple', jest.fn(), jest.fn(), false]); }); - it('renders Interlinearizer and the nav controls when book data is available', () => { + it('shows nav controls when interface mode is power', () => { + jest.mocked(useSetting).mockReturnValue(['power', jest.fn(), jest.fn(), false]); render( { expect(screen.getByTestId('interlinearizer')).toBeInTheDocument(); }); + it('hides nav controls when interface mode is simple', () => { + render( + , + ); + + expect(screen.queryByTestId('scripture-nav-controls')).not.toBeInTheDocument(); + expect(screen.getByTestId('interlinearizer')).toBeInTheDocument(); + }); + it('shows Loading when book data has not arrived', () => { mockBookData({ book: undefined, isLoading: true }); render( diff --git a/src/components/InterlinearizerLoader.tsx b/src/components/InterlinearizerLoader.tsx index a3e8a1ec..56a011ec 100644 --- a/src/components/InterlinearizerLoader.tsx +++ b/src/components/InterlinearizerLoader.tsx @@ -1,5 +1,5 @@ import type { UseWebViewScrollGroupScrRefHook } from '@papi/core'; -import { useLocalizedStrings } from '@papi/frontend/react'; +import { useLocalizedStrings, useSetting } from '@papi/frontend/react'; import { TabToolbar } from 'platform-bible-react'; import { useMemo } from 'react'; import ContinuousScrollToggle from './ContinuousScrollToggle'; @@ -29,6 +29,8 @@ export default function InterlinearizerLoader({ }>) { const [scrRef, setScrRef, scrollGroupId, setScrollGroupId] = useWebViewScrollGroupScrRef(); + const [interfaceMode] = useSetting('platform.interfaceMode', 'simple'); + const { isLoading: isSettingLoading, onChange: handleContinuousScrollChange, @@ -51,12 +53,14 @@ export default function InterlinearizerLoader({ + interfaceMode === 'power' ? ( + + ) : undefined } endAreaChildren={