diff --git a/.changeset/twenty-onions-wish.md b/.changeset/twenty-onions-wish.md
new file mode 100644
index 00000000..476b957d
--- /dev/null
+++ b/.changeset/twenty-onions-wish.md
@@ -0,0 +1,13 @@
+---
+'@youversion/platform-react-hooks': minor
+'@youversion/platform-core': minor
+'@youversion/platform-react-ui': minor
+---
+
+Deprecates hooks and providers that are not used by the UI package or any known consumers. These were inherited from a hackathon project and never adopted. These will be fully removed in the next major version bump.
+
+Deprecated:
+- `useInitData` — convenience wrapper over `useVersion`, `useBook`, `useChapter` that loses error granularity, drops `refetch`, and has zero consumers. Use the three hooks directly.
+- `useChapterNavigation` — coupled to `ReaderProvider` which nobody uses. The UI package calls `getAdjacentChapter` from core directly.
+- `ReaderProvider`, `ReaderContext`, `useReaderContext` — the UI package built its own `BibleReaderContext` instead. Zero consumers.
+- `VerseSelectionProvider`, `VerseSelectionContext`, `useVerseSelection` — the UI package handles verse selection via props/callbacks. Zero consumers.
diff --git a/greptile.json b/greptile.json
index 2c1059eb..da913071 100644
--- a/greptile.json
+++ b/greptile.json
@@ -16,7 +16,7 @@
"rule": "Schema-first: All types defined in schemas/*.ts using Zod"
},
{
- "scope": ["packages/**"],
+ "scope": ["packages/**", ".changeset/**"],
"rule": "Unified versioning: All packages must maintain exact same version - never version packages independently"
},
{
diff --git a/packages/hooks/AGENTS.md b/packages/hooks/AGENTS.md
index 0ee13a52..d7af5adb 100644
--- a/packages/hooks/AGENTS.md
+++ b/packages/hooks/AGENTS.md
@@ -1,7 +1,7 @@
# @youversion/platform-react-hooks
## OVERVIEW
-React integration layer providing data fetching hooks with 3 core providers: YouVersionProvider, YouVersionAuthProvider, and ReaderProvider.
+React integration layer providing data fetching hooks with 2 core providers: YouVersionProvider and YouVersionAuthProvider.
**Depends on `@youversion/platform-core` for all API calls.** Hooks delegate to core clients; do not implement raw HTTP here.
@@ -15,12 +15,12 @@ React integration layer providing data fetching hooks with 3 core providers: You
- `utility/` - Helper functions (useDebounce, extractTextFromHTML, extractVersesFromHTML)
## PUBLIC API
-- Data fetching hooks: useBook, useChapter, usePassage, useVersion, useVOTD, useVerse, useChapterNavigation, etc.
+- Data fetching hooks: useBook, useChapter, usePassage, useVersion, useVOTD, useVerse, etc.
- YouVersionProvider - Core SDK configuration
- YouVersionAuthProvider - Authentication state
-- ReaderProvider - Reading session context
- Utility functions exported from utility/index
+
## PROVIDERS
- **YouVersionProvider**
@@ -31,10 +31,6 @@ React integration layer providing data fetching hooks with 3 core providers: You
- Manages authentication state (userInfo, tokens, isLoading, error)
- Auth hooks like `useYVAuth` depend on this provider
-- **ReaderProvider**
- - Manages Bible reading session state (currentVersion, currentChapter, currentBook, currentVerse)
- - Hooks like `useChapterNavigation` depend on this provider
-
## DOs / DON'Ts
✅ Do: Use `YouVersionProvider` for configuration and access that config in hooks
diff --git a/packages/hooks/src/context/ReaderContext.test.tsx b/packages/hooks/src/context/ReaderContext.test.tsx
deleted file mode 100644
index 4b441a41..00000000
--- a/packages/hooks/src/context/ReaderContext.test.tsx
+++ /dev/null
@@ -1,153 +0,0 @@
-import { describe, it, expect, vi } from 'vitest';
-import { renderHook, act } from '@testing-library/react';
-import React, { useContext } from 'react';
-import { ReaderContext, useReaderContext } from './ReaderContext';
-import { ReaderProvider } from './ReaderProvider';
-import {
- createMockBook,
- createMockChapter,
- createMockVerse,
- createMockVersion,
-} from '../__tests__/mocks/bibles';
-
-const mockVersion = createMockVersion();
-const mockBook = createMockBook();
-const mockChapter = createMockChapter();
-const mockVerse = createMockVerse();
-
-const mockVersion2 = createMockVersion({
- id: 2,
- title: 'New International Version',
- abbreviation: 'NIV',
- localized_abbreviation: 'NIV',
- localized_title: 'New International Version',
- youversion_deep_link: 'https://www.bible.com/versions/2',
-});
-
-const mockBook2 = createMockBook({
- id: 'JHN',
- title: 'John',
- full_title: 'The Gospel According to John',
- abbreviation: 'Jn',
- canon: 'new_testament',
-});
-
-const mockChapter2 = createMockChapter({
- id: '3',
- passage_id: 'JHN.3',
- title: '3',
-});
-
-const mockVerse2 = createMockVerse({
- id: '16',
- passage_id: 'JHN.3.16',
- title: '16',
-});
-
-const wrapper = ({ children }: { children: React.ReactNode }) => (
-
- {children}
-
-);
-
-describe('ReaderContext', () => {
- describe('createContext default', () => {
- it('should have null as default context value', () => {
- const { result } = renderHook(() => useContext(ReaderContext));
-
- expect(result.current).toBeNull();
- });
- });
-
- describe('useReaderContext', () => {
- it('should throw error when used outside ReaderProvider', () => {
- const consoleError = vi.spyOn(console, 'error').mockImplementation(() => undefined);
-
- expect(() => renderHook(() => useReaderContext())).toThrow(
- 'useReaderContext() must be used within a ReaderProvider',
- );
-
- consoleError.mockRestore();
- });
-
- it('should return context with all expected keys', () => {
- const { result } = renderHook(() => useReaderContext(), { wrapper });
- const keys = Object.keys(result.current);
-
- expect(keys).toContain('currentVersion');
- expect(keys).toContain('currentChapter');
- expect(keys).toContain('currentBook');
- expect(keys).toContain('currentVerse');
- expect(keys).toContain('setVersion');
- expect(keys).toContain('setChapter');
- expect(keys).toContain('setBook');
- expect(keys).toContain('setVerse');
- });
-
- it('should return functions for all setters', () => {
- const { result } = renderHook(() => useReaderContext(), { wrapper });
-
- expect(typeof result.current.setVersion).toBe('function');
- expect(typeof result.current.setChapter).toBe('function');
- expect(typeof result.current.setBook).toBe('function');
- expect(typeof result.current.setVerse).toBe('function');
- });
-
- describe('setter updates', () => {
- it('should update currentVersion when setVersion is called', () => {
- const { result } = renderHook(() => useReaderContext(), { wrapper });
-
- act(() => {
- result.current.setVersion(mockVersion2);
- });
-
- expect(result.current.currentVersion).toEqual(mockVersion2);
- });
-
- it('should update currentBook when setBook is called', () => {
- const { result } = renderHook(() => useReaderContext(), { wrapper });
-
- act(() => {
- result.current.setBook(mockBook2);
- });
-
- expect(result.current.currentBook).toEqual(mockBook2);
- });
-
- it('should update currentChapter when setChapter is called', () => {
- const { result } = renderHook(() => useReaderContext(), { wrapper });
-
- act(() => {
- result.current.setChapter(mockChapter2);
- });
-
- expect(result.current.currentChapter).toEqual(mockChapter2);
- });
-
- it('should update currentVerse when setVerse is called', () => {
- const { result } = renderHook(() => useReaderContext(), { wrapper });
-
- act(() => {
- result.current.setVerse(mockVerse2);
- });
-
- expect(result.current.currentVerse).toEqual(mockVerse2);
- });
-
- it('should set currentVerse to null', () => {
- const { result } = renderHook(() => useReaderContext(), { wrapper });
-
- act(() => {
- result.current.setVerse(null);
- });
-
- expect(result.current.currentVerse).toBeNull();
- });
- });
- });
-});
diff --git a/packages/hooks/src/context/ReaderContext.tsx b/packages/hooks/src/context/ReaderContext.tsx
index 1b92b067..2691c6d8 100644
--- a/packages/hooks/src/context/ReaderContext.tsx
+++ b/packages/hooks/src/context/ReaderContext.tsx
@@ -14,8 +14,14 @@ type ReaderContextData = {
setVerse: (verse: BibleVerse | null) => void;
};
+/**
+ * @deprecated No replacement needed. Remove usage. Will be removed in the next major version.
+ */
export const ReaderContext = createContext(null);
+/**
+ * @deprecated No replacement needed. Remove usage. Will be removed in the next major version.
+ */
export function useReaderContext(): ReaderContextData {
const context = useContext(ReaderContext);
diff --git a/packages/hooks/src/context/ReaderProvider.test.tsx b/packages/hooks/src/context/ReaderProvider.test.tsx
deleted file mode 100644
index 1a992fe6..00000000
--- a/packages/hooks/src/context/ReaderProvider.test.tsx
+++ /dev/null
@@ -1,264 +0,0 @@
-import { describe, it, expect, vi } from 'vitest';
-import { renderHook, act } from '@testing-library/react';
-import React from 'react';
-import { useReaderContext } from './ReaderContext';
-import { ReaderProvider } from './ReaderProvider';
-import {
- createMockBook,
- createMockChapter,
- createMockVerse,
- createMockVersion,
-} from '../__tests__/mocks/bibles';
-
-// Mock Bible data
-const mockVersion = createMockVersion();
-const mockBook = createMockBook();
-const mockChapter = createMockChapter();
-const mockVerse = createMockVerse();
-
-// Alternative mock data for update tests
-const mockVersion2 = createMockVersion({
- id: 2,
- title: 'New International Version',
- abbreviation: 'NIV',
- localized_abbreviation: 'NIV',
- localized_title: 'New International Version',
- youversion_deep_link: 'https://www.bible.com/versions/2',
-});
-
-const mockBook2 = createMockBook({
- id: 'JHN',
- title: 'John',
- full_title: 'The Gospel According to John',
- abbreviation: 'Jn',
- canon: 'new_testament',
-});
-
-const mockChapter2 = createMockChapter({
- id: '3',
- passage_id: 'JHN.3',
- title: '3',
-});
-
-const mockVerse2 = createMockVerse({
- id: '16',
- passage_id: 'JHN.3.16',
- title: '16',
-});
-
-// Test wrappers
-const wrapper = ({ children }: { children: React.ReactNode }) => (
-
- {children}
-
-);
-
-const wrapperWithNullVerse = ({ children }: { children: React.ReactNode }) => (
-
- {children}
-
-);
-
-describe('ReaderProvider', () => {
- describe('initialization', () => {
- it('should initialize with provided version', () => {
- const { result } = renderHook(() => useReaderContext(), { wrapper });
-
- expect(result.current.currentVersion).toEqual(mockVersion);
- });
-
- it('should initialize with provided book', () => {
- const { result } = renderHook(() => useReaderContext(), { wrapper });
-
- expect(result.current.currentBook).toEqual(mockBook);
- });
-
- it('should initialize with provided chapter', () => {
- const { result } = renderHook(() => useReaderContext(), { wrapper });
-
- expect(result.current.currentChapter).toEqual(mockChapter);
- });
-
- it('should initialize with provided verse', () => {
- const { result } = renderHook(() => useReaderContext(), { wrapper });
-
- expect(result.current.currentVerse).toEqual(mockVerse);
- });
-
- it('should initialize with null verse when provided', () => {
- const { result } = renderHook(() => useReaderContext(), { wrapper: wrapperWithNullVerse });
-
- expect(result.current.currentVerse).toBeNull();
- });
-
- it('should throw error when useReaderContext is used outside provider', () => {
- const consoleError = vi.spyOn(console, 'error').mockImplementation(() => undefined);
-
- expect(() => renderHook(() => useReaderContext())).toThrow(
- 'useReaderContext() must be used within a ReaderProvider',
- );
-
- consoleError.mockRestore();
- });
- });
-
- describe('setVersion', () => {
- it('should update version when setVersion is called', () => {
- const { result } = renderHook(() => useReaderContext(), { wrapper });
-
- act(() => {
- result.current.setVersion(mockVersion2);
- });
-
- expect(result.current.currentVersion).toEqual(mockVersion2);
- });
-
- it('should preserve other state when version changes', () => {
- const { result } = renderHook(() => useReaderContext(), { wrapper });
-
- act(() => {
- result.current.setVersion(mockVersion2);
- });
-
- expect(result.current.currentVersion).toMatchObject({ abbreviation: 'NIV' });
- expect(result.current.currentBook).toEqual(mockBook);
- expect(result.current.currentChapter).toEqual(mockChapter);
- expect(result.current.currentVerse).toEqual(mockVerse);
- });
- });
-
- describe('setBook', () => {
- it('should update book when setBook is called', () => {
- const { result } = renderHook(() => useReaderContext(), { wrapper });
-
- act(() => {
- result.current.setBook(mockBook2);
- });
-
- expect(result.current.currentBook).toEqual(mockBook2);
- });
-
- it('should preserve other state when book changes', () => {
- const { result } = renderHook(() => useReaderContext(), { wrapper });
-
- act(() => {
- result.current.setBook(mockBook2);
- });
-
- expect(result.current.currentBook).toMatchObject({ id: 'JHN' });
- expect(result.current.currentVersion).toEqual(mockVersion);
- expect(result.current.currentChapter).toEqual(mockChapter);
- expect(result.current.currentVerse).toEqual(mockVerse);
- });
- });
-
- describe('setChapter', () => {
- it('should update chapter when setChapter is called', () => {
- const { result } = renderHook(() => useReaderContext(), { wrapper });
-
- act(() => {
- result.current.setChapter(mockChapter2);
- });
-
- expect(result.current.currentChapter).toEqual(mockChapter2);
- });
-
- it('should preserve other state when chapter changes', () => {
- const { result } = renderHook(() => useReaderContext(), { wrapper });
-
- act(() => {
- result.current.setChapter(mockChapter2);
- });
-
- expect(result.current.currentChapter).toMatchObject({ id: '3' });
- expect(result.current.currentVersion).toEqual(mockVersion);
- expect(result.current.currentBook).toEqual(mockBook);
- expect(result.current.currentVerse).toEqual(mockVerse);
- });
- });
-
- describe('setVerse', () => {
- it('should update verse when setVerse is called', () => {
- const { result } = renderHook(() => useReaderContext(), { wrapper });
-
- act(() => {
- result.current.setVerse(mockVerse2);
- });
-
- expect(result.current.currentVerse).toEqual(mockVerse2);
- });
-
- it('should update verse to null when setVerse is called with null', () => {
- const { result } = renderHook(() => useReaderContext(), { wrapper });
-
- act(() => {
- result.current.setVerse(null);
- });
-
- expect(result.current.currentVerse).toBeNull();
- });
-
- it('should preserve other state when verse changes', () => {
- const { result } = renderHook(() => useReaderContext(), { wrapper });
-
- act(() => {
- result.current.setVerse(mockVerse2);
- });
-
- expect(result.current.currentVerse).toMatchObject({ id: '16' });
- expect(result.current.currentVersion).toEqual(mockVersion);
- expect(result.current.currentBook).toEqual(mockBook);
- expect(result.current.currentChapter).toEqual(mockChapter);
- });
-
- it('should preserve other state when verse changes to null', () => {
- const { result } = renderHook(() => useReaderContext(), { wrapper });
-
- act(() => {
- result.current.setVerse(null);
- });
-
- expect(result.current.currentVerse).toBeNull();
- expect(result.current.currentVersion).toEqual(mockVersion);
- expect(result.current.currentBook).toEqual(mockBook);
- expect(result.current.currentChapter).toEqual(mockChapter);
- });
- });
-
- describe('state persistence', () => {
- it('should initialize with all provided props', () => {
- const { result } = renderHook(() => useReaderContext(), { wrapper });
-
- expect(result.current.currentVersion).toEqual(mockVersion);
- expect(result.current.currentBook).toEqual(mockBook);
- expect(result.current.currentChapter).toEqual(mockChapter);
- expect(result.current.currentVerse).toEqual(mockVerse);
- });
-
- it('should handle rapid successive state updates without corruption', () => {
- const { result } = renderHook(() => useReaderContext(), { wrapper });
-
- act(() => {
- result.current.setVersion(mockVersion2);
- result.current.setBook(mockBook2);
- result.current.setChapter(mockChapter2);
- result.current.setVerse(mockVerse2);
- });
-
- expect(result.current.currentVersion).toEqual(mockVersion2);
- expect(result.current.currentBook).toEqual(mockBook2);
- expect(result.current.currentChapter).toEqual(mockChapter2);
- expect(result.current.currentVerse).toEqual(mockVerse2);
- });
- });
-});
diff --git a/packages/hooks/src/context/ReaderProvider.tsx b/packages/hooks/src/context/ReaderProvider.tsx
index 1f07347b..cdf5dbb9 100644
--- a/packages/hooks/src/context/ReaderProvider.tsx
+++ b/packages/hooks/src/context/ReaderProvider.tsx
@@ -11,6 +11,9 @@ type ReaderProviderProps = {
currentVerse: BibleVerse | null;
};
+/**
+ * @deprecated No replacement needed. Remove usage. Will be removed in the next major version.
+ */
export function ReaderProvider(props: PropsWithChildren): React.ReactElement {
const [currentVersion, setCurrentVersion] = useState(props.currentVersion);
const [currentBook, setCurrentBook] = useState(props.currentBook);
diff --git a/packages/hooks/src/context/VerseSelectionContext.tsx b/packages/hooks/src/context/VerseSelectionContext.tsx
index 86eacf38..697583a4 100644
--- a/packages/hooks/src/context/VerseSelectionContext.tsx
+++ b/packages/hooks/src/context/VerseSelectionContext.tsx
@@ -1,5 +1,8 @@
import { createContext } from 'react';
+/**
+ * @deprecated No replacement needed. Remove usage. Will be removed in the next major version.
+ */
export type VerseSelectionContextData = {
selectedVerseUsfms: Set;
toggleVerse: (usfm: string) => void;
@@ -8,4 +11,7 @@ export type VerseSelectionContextData = {
selectedCount: number;
};
+/**
+ * @deprecated No replacement needed. Remove usage. Will be removed in the next major version.
+ */
export const VerseSelectionContext = createContext(null);
diff --git a/packages/hooks/src/context/VerseSelectionProvider.test.tsx b/packages/hooks/src/context/VerseSelectionProvider.test.tsx
deleted file mode 100644
index ed825ef5..00000000
--- a/packages/hooks/src/context/VerseSelectionProvider.test.tsx
+++ /dev/null
@@ -1,362 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import { renderHook, act } from '@testing-library/react';
-import type { ReactNode } from 'react';
-import { VerseSelectionProvider } from './VerseSelectionProvider';
-import { useVerseSelection } from '../useVerseSelection';
-
-// Wrapper for renderHook
-const wrapper = ({ children }: { children: ReactNode }) => (
- {children}
-);
-
-describe('VerseSelectionProvider', () => {
- describe('initial state', () => {
- it('should provide an empty Set instance', () => {
- const { result } = renderHook(() => useVerseSelection(), { wrapper });
-
- expect(result.current.selectedVerseUsfms).toBeInstanceOf(Set);
- expect(result.current.selectedVerseUsfms.size).toBe(0);
- });
-
- it('should have selectedCount of 0', () => {
- const { result } = renderHook(() => useVerseSelection(), { wrapper });
-
- expect(result.current.selectedCount).toBe(0);
- });
-
- it('should return false for isSelected on any verse', () => {
- const { result } = renderHook(() => useVerseSelection(), { wrapper });
-
- expect(result.current.isSelected('MAT.1.1')).toBe(false);
- expect(result.current.isSelected('GEN.1.1')).toBe(false);
- });
- });
-
- describe('toggleVerse', () => {
- it('should add verse to selection when not present', () => {
- const { result } = renderHook(() => useVerseSelection(), { wrapper });
-
- act(() => {
- result.current.toggleVerse('MAT.1.1');
- });
-
- expect(result.current.selectedVerseUsfms.has('MAT.1.1')).toBe(true);
- expect(result.current.selectedCount).toBe(1);
- });
-
- it('should remove verse from selection when already present', () => {
- const { result } = renderHook(() => useVerseSelection(), { wrapper });
-
- act(() => {
- result.current.toggleVerse('MAT.1.1');
- });
-
- expect(result.current.selectedCount).toBe(1);
-
- act(() => {
- result.current.toggleVerse('MAT.1.1');
- });
-
- expect(result.current.selectedVerseUsfms.has('MAT.1.1')).toBe(false);
- expect(result.current.selectedCount).toBe(0);
- });
-
- it('should support multiple verses selected simultaneously', () => {
- const { result } = renderHook(() => useVerseSelection(), { wrapper });
-
- act(() => {
- result.current.toggleVerse('MAT.1.1');
- result.current.toggleVerse('GEN.1.1');
- result.current.toggleVerse('JHN.3.16');
- });
-
- expect(result.current.selectedCount).toBe(3);
- expect(result.current.selectedVerseUsfms.has('MAT.1.1')).toBe(true);
- expect(result.current.selectedVerseUsfms.has('GEN.1.1')).toBe(true);
- expect(result.current.selectedVerseUsfms.has('JHN.3.16')).toBe(true);
- });
-
- it('should create new Set reference on each update (immutability)', () => {
- const { result } = renderHook(() => useVerseSelection(), { wrapper });
-
- const set1 = result.current.selectedVerseUsfms;
-
- act(() => {
- result.current.toggleVerse('MAT.1.1');
- });
-
- const set2 = result.current.selectedVerseUsfms;
-
- expect(set1).not.toBe(set2);
- expect(set1.size).toBe(0);
- expect(set2.size).toBe(1);
- });
-
- it('should create new Set reference when removing a verse', () => {
- const { result } = renderHook(() => useVerseSelection(), { wrapper });
-
- act(() => {
- result.current.toggleVerse('MAT.1.1');
- });
-
- const setWithVerse = result.current.selectedVerseUsfms;
-
- act(() => {
- result.current.toggleVerse('MAT.1.1');
- });
-
- const setWithoutVerse = result.current.selectedVerseUsfms;
-
- expect(setWithVerse).not.toBe(setWithoutVerse);
- expect(setWithVerse.size).toBe(1);
- expect(setWithoutVerse.size).toBe(0);
- });
-
- it('should not add duplicate verses', () => {
- const { result } = renderHook(() => useVerseSelection(), { wrapper });
-
- act(() => {
- result.current.toggleVerse('MAT.1.1');
- result.current.toggleVerse('MAT.1.1');
- });
-
- // Should toggle off, not add twice
- expect(result.current.selectedCount).toBe(0);
- });
- });
-
- describe('isSelected', () => {
- it('should return true for selected verses', () => {
- const { result } = renderHook(() => useVerseSelection(), { wrapper });
-
- act(() => {
- result.current.toggleVerse('MAT.1.1');
- });
-
- expect(result.current.isSelected('MAT.1.1')).toBe(true);
- });
-
- it('should return false for unselected verses', () => {
- const { result } = renderHook(() => useVerseSelection(), { wrapper });
-
- act(() => {
- result.current.toggleVerse('MAT.1.1');
- });
-
- expect(result.current.isSelected('GEN.1.1')).toBe(false);
- });
-
- it('should work correctly after toggle operations', () => {
- const { result } = renderHook(() => useVerseSelection(), { wrapper });
-
- // Initially not selected
- expect(result.current.isSelected('MAT.1.1')).toBe(false);
-
- // Add verse - now selected
- act(() => {
- result.current.toggleVerse('MAT.1.1');
- });
- expect(result.current.isSelected('MAT.1.1')).toBe(true);
-
- // Remove verse - not selected again
- act(() => {
- result.current.toggleVerse('MAT.1.1');
- });
- expect(result.current.isSelected('MAT.1.1')).toBe(false);
- });
- });
-
- describe('clearSelection', () => {
- it('should clear all selected verses', () => {
- const { result } = renderHook(() => useVerseSelection(), { wrapper });
-
- act(() => {
- result.current.toggleVerse('MAT.1.1');
- result.current.toggleVerse('GEN.1.1');
- });
-
- expect(result.current.selectedCount).toBe(2);
-
- act(() => {
- result.current.clearSelection();
- });
-
- expect(result.current.selectedCount).toBe(0);
- expect(result.current.selectedVerseUsfms.size).toBe(0);
- });
-
- it('should create new Set reference', () => {
- const { result } = renderHook(() => useVerseSelection(), { wrapper });
-
- act(() => {
- result.current.toggleVerse('MAT.1.1');
- });
-
- const setBeforeClear = result.current.selectedVerseUsfms;
-
- act(() => {
- result.current.clearSelection();
- });
-
- const setAfterClear = result.current.selectedVerseUsfms;
-
- expect(setBeforeClear).not.toBe(setAfterClear);
- expect(setBeforeClear.size).toBe(1);
- expect(setAfterClear.size).toBe(0);
- });
-
- it('should work when already empty', () => {
- const { result } = renderHook(() => useVerseSelection(), { wrapper });
-
- const setBeforeClear = result.current.selectedVerseUsfms;
-
- act(() => {
- result.current.clearSelection();
- });
-
- const setAfterClear = result.current.selectedVerseUsfms;
-
- expect(setBeforeClear).not.toBe(setAfterClear);
- expect(setAfterClear.size).toBe(0);
- });
- });
-
- describe('selectedCount', () => {
- it('should start at 0', () => {
- const { result } = renderHook(() => useVerseSelection(), { wrapper });
-
- expect(result.current.selectedCount).toBe(0);
- });
-
- it('should increment when verse added', () => {
- const { result } = renderHook(() => useVerseSelection(), { wrapper });
-
- act(() => {
- result.current.toggleVerse('MAT.1.1');
- });
- expect(result.current.selectedCount).toBe(1);
-
- act(() => {
- result.current.toggleVerse('GEN.1.1');
- });
- expect(result.current.selectedCount).toBe(2);
-
- act(() => {
- result.current.toggleVerse('JHN.3.16');
- });
- expect(result.current.selectedCount).toBe(3);
- });
-
- it('should decrement when verse removed', () => {
- const { result } = renderHook(() => useVerseSelection(), { wrapper });
-
- act(() => {
- result.current.toggleVerse('MAT.1.1');
- result.current.toggleVerse('GEN.1.1');
- });
- expect(result.current.selectedCount).toBe(2);
-
- act(() => {
- result.current.toggleVerse('MAT.1.1');
- });
- expect(result.current.selectedCount).toBe(1);
- });
-
- it('should return to 0 after clear', () => {
- const { result } = renderHook(() => useVerseSelection(), { wrapper });
-
- act(() => {
- result.current.toggleVerse('MAT.1.1');
- result.current.toggleVerse('GEN.1.1');
- result.current.toggleVerse('JHN.3.16');
- });
- expect(result.current.selectedCount).toBe(3);
-
- act(() => {
- result.current.clearSelection();
- });
- expect(result.current.selectedCount).toBe(0);
- });
- });
-
- describe('callback stability', () => {
- it('should have stable toggleVerse reference across renders', () => {
- const { result, rerender } = renderHook(() => useVerseSelection(), { wrapper });
-
- const toggleRef1 = result.current.toggleVerse;
-
- rerender();
-
- const toggleRef2 = result.current.toggleVerse;
-
- expect(toggleRef1).toBe(toggleRef2);
- });
-
- it('should have stable clearSelection reference across renders', () => {
- const { result, rerender } = renderHook(() => useVerseSelection(), { wrapper });
-
- const clearRef1 = result.current.clearSelection;
-
- rerender();
-
- const clearRef2 = result.current.clearSelection;
-
- expect(clearRef1).toBe(clearRef2);
- });
-
- it('should have stable toggleVerse reference after state changes', () => {
- const { result } = renderHook(() => useVerseSelection(), { wrapper });
-
- const toggleRef1 = result.current.toggleVerse;
-
- act(() => {
- result.current.toggleVerse('MAT.1.1');
- });
-
- const toggleRef2 = result.current.toggleVerse;
-
- expect(toggleRef1).toBe(toggleRef2);
- });
-
- it('should have stable clearSelection reference after state changes', () => {
- const { result } = renderHook(() => useVerseSelection(), { wrapper });
-
- const clearRef1 = result.current.clearSelection;
-
- act(() => {
- result.current.toggleVerse('MAT.1.1');
- });
-
- const clearRef2 = result.current.clearSelection;
-
- expect(clearRef1).toBe(clearRef2);
- });
-
- it('should update isSelected reference when selectedVerseUsfms changes', () => {
- const { result } = renderHook(() => useVerseSelection(), { wrapper });
-
- const isSelectedRef1 = result.current.isSelected;
-
- act(() => {
- result.current.toggleVerse('MAT.1.1');
- });
-
- const isSelectedRef2 = result.current.isSelected;
-
- // isSelected has selectedVerseUsfms in its dependency array, so it should change
- expect(isSelectedRef1).not.toBe(isSelectedRef2);
- });
-
- it('should have stable isSelected reference when no state changes', () => {
- const { result, rerender } = renderHook(() => useVerseSelection(), { wrapper });
-
- const isSelectedRef1 = result.current.isSelected;
-
- rerender();
-
- const isSelectedRef2 = result.current.isSelected;
-
- expect(isSelectedRef1).toBe(isSelectedRef2);
- });
- });
-});
diff --git a/packages/hooks/src/context/VerseSelectionProvider.tsx b/packages/hooks/src/context/VerseSelectionProvider.tsx
index c5937660..dc025b8b 100644
--- a/packages/hooks/src/context/VerseSelectionProvider.tsx
+++ b/packages/hooks/src/context/VerseSelectionProvider.tsx
@@ -1,6 +1,9 @@
import { type PropsWithChildren, useCallback, useState } from 'react';
import { VerseSelectionContext } from './VerseSelectionContext';
+/**
+ * @deprecated No replacement needed. Remove usage. Will be removed in the next major version.
+ */
export function VerseSelectionProvider({ children }: PropsWithChildren): React.ReactElement {
const [selectedVerseUsfms, setSelectedVerseUsfms] = useState>(new Set());
diff --git a/packages/hooks/src/useChapterNavigation.test.tsx b/packages/hooks/src/useChapterNavigation.test.tsx
deleted file mode 100644
index 1fcd9c78..00000000
--- a/packages/hooks/src/useChapterNavigation.test.tsx
+++ /dev/null
@@ -1,161 +0,0 @@
-import { describe, it, expect, vi, beforeEach } from 'vitest';
-import { renderHook, act } from '@testing-library/react';
-import React from 'react';
-import { useChapterNavigation } from './useChapterNavigation';
-import { useReaderContext } from './context/ReaderContext';
-import { ReaderProvider } from './context/ReaderProvider';
-import { createMockBook, createMockVersion } from './__tests__/mocks/bibles';
-import type { BibleBook, BibleChapter } from '@youversion/platform-core';
-
-function makeChapters(bookId: string, count: number): BibleChapter[] {
- return Array.from({ length: count }, (_, i) => ({
- id: (i + 1).toString(),
- passage_id: `${bookId}.${i + 1}`,
- title: (i + 1).toString(),
- }));
-}
-
-const genChapters = makeChapters('GEN', 50);
-const exoChapters = makeChapters('EXO', 40);
-const revChapters = makeChapters('REV', 22);
-
-const mockBooks: BibleBook[] = [
- createMockBook({
- id: 'GEN',
- title: 'Genesis',
- chapters: genChapters,
- intro: { id: 'INTRO', passage_id: 'GEN.INTRO', title: 'Intro' },
- }),
- createMockBook({
- id: 'EXO',
- title: 'Exodus',
- canon: 'old_testament',
- chapters: exoChapters,
- }),
- createMockBook({
- id: 'REV',
- title: 'Revelation',
- canon: 'new_testament',
- chapters: revChapters,
- }),
-];
-
-const { mockUseBooks } = vi.hoisted(() => ({
- mockUseBooks: vi.fn(),
-}));
-vi.mock('./useBooks', () => ({
- useBooks: mockUseBooks,
-}));
-
-function useNavWithContext() {
- const nav = useChapterNavigation();
- const ctx = useReaderContext();
- return { nav, ctx };
-}
-
-function wrapper(book: BibleBook, chapter: BibleChapter) {
- return ({ children }: { children: React.ReactNode }) => (
-
- {children}
-
- );
-}
-
-const genBook = mockBooks[0]!;
-const exoBook = mockBooks[1]!;
-const revBook = mockBooks[2]!;
-
-describe('useChapterNavigation', () => {
- beforeEach(() => {
- mockUseBooks.mockReturnValue({
- books: { data: mockBooks },
- loading: false,
- error: null,
- refetch: vi.fn(),
- });
- });
-
- it('navigateToNext within same book updates chapter, keeps book', () => {
- const { result } = renderHook(useNavWithContext, {
- wrapper: wrapper(genBook, genChapters[0]!),
- });
-
- act(() => result.current.nav.navigateToNext());
-
- expect(result.current.ctx.currentChapter.id).toBe('2');
- expect(result.current.ctx.currentBook.id).toBe('GEN');
- });
-
- it('navigateToNext cross-book updates both book and chapter', () => {
- const { result } = renderHook(useNavWithContext, {
- wrapper: wrapper(genBook, genChapters.at(-1)!),
- });
-
- act(() => result.current.nav.navigateToNext());
-
- expect(result.current.ctx.currentBook.id).toBe('EXO');
- expect(result.current.ctx.currentChapter.id).toBe('1');
- });
-
- it('navigateToPrevious to intro sets chapter to INTRO, keeps book', () => {
- const { result } = renderHook(useNavWithContext, {
- wrapper: wrapper(genBook, genChapters[0]!),
- });
-
- act(() => result.current.nav.navigateToPrevious());
-
- expect(result.current.ctx.currentChapter.id).toBe('INTRO');
- expect(result.current.ctx.currentBook.id).toBe('GEN');
- });
-
- it('navigateToPrevious cross-book updates both book and chapter', () => {
- const { result } = renderHook(useNavWithContext, {
- wrapper: wrapper(exoBook, exoChapters[0]!),
- });
-
- act(() => result.current.nav.navigateToPrevious());
-
- expect(result.current.ctx.currentBook.id).toBe('GEN');
- expect(result.current.ctx.currentChapter.id).toBe('50');
- });
-
- it('canNavigatePrevious is false at Bible start', () => {
- const introChapter: BibleChapter = { id: 'INTRO', passage_id: 'GEN.INTRO', title: 'Intro' };
-
- const { result } = renderHook(useNavWithContext, {
- wrapper: wrapper(genBook, introChapter),
- });
-
- expect(result.current.nav.canNavigatePrevious).toBe(false);
- });
-
- it('canNavigateNext is false at Bible end', () => {
- const { result } = renderHook(useNavWithContext, {
- wrapper: wrapper(revBook, revChapters[21]!),
- });
-
- expect(result.current.nav.canNavigateNext).toBe(false);
- });
-
- it('both disabled while loading', () => {
- mockUseBooks.mockReturnValue({
- books: null,
- loading: true,
- error: null,
- refetch: vi.fn(),
- });
-
- const { result } = renderHook(useNavWithContext, {
- wrapper: wrapper(genBook, genChapters[0]!),
- });
-
- expect(result.current.nav.canNavigateNext).toBe(false);
- expect(result.current.nav.canNavigatePrevious).toBe(false);
- expect(result.current.nav.isLoading).toBe(true);
- });
-});
diff --git a/packages/hooks/src/useChapterNavigation.ts b/packages/hooks/src/useChapterNavigation.ts
index 6c5d2ad3..cf3f37ed 100644
--- a/packages/hooks/src/useChapterNavigation.ts
+++ b/packages/hooks/src/useChapterNavigation.ts
@@ -16,6 +16,9 @@ interface UseChapterNavigationResult {
/**
* Provides navigation functionality for chapters across book boundaries,
* including intro chapter support.
+ *
+ * @deprecated This hook will be removed in the next major version.
+ * Use `getAdjacentChapter` from `@youversion/platform-core` directly instead.
*/
export function useChapterNavigation(): UseChapterNavigationResult {
const { currentChapter, currentVersion, currentBook, setChapter, setBook } = useReaderContext();
diff --git a/packages/hooks/src/useInitData.ts b/packages/hooks/src/useInitData.ts
index 062ec800..5120b5d5 100644
--- a/packages/hooks/src/useInitData.ts
+++ b/packages/hooks/src/useInitData.ts
@@ -22,6 +22,10 @@ interface InitData {
chapter: BibleChapter;
}
+/**
+ * @deprecated This hook will be removed in the next major version.
+ * Use `useVersion`, `useBook`, and `useChapter` directly instead.
+ */
export function useInitData(
{ version, book, chapter }: Props = {
version: DEFAULT.VERSION,
diff --git a/packages/hooks/src/useVerseSelection.test.tsx b/packages/hooks/src/useVerseSelection.test.tsx
deleted file mode 100644
index 7e610560..00000000
--- a/packages/hooks/src/useVerseSelection.test.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { describe, it, expect, vi } from 'vitest';
-import { renderHook } from '@testing-library/react';
-import type { ReactNode } from 'react';
-import { useVerseSelection } from './useVerseSelection';
-import { VerseSelectionProvider } from './context/VerseSelectionProvider';
-
-// Wrapper for renderHook
-const wrapper = ({ children }: { children: ReactNode }) => (
- {children}
-);
-
-describe('useVerseSelection', () => {
- it('should throw error when used outside provider', () => {
- const consoleSpy = vi.spyOn(console, 'error').mockImplementation(vi.fn());
-
- expect(() => {
- renderHook(() => useVerseSelection());
- }).toThrow('useVerseSelection must be used within a VerseSelectionProvider');
-
- consoleSpy.mockRestore();
- });
-
- it('should return expected shape with correct initial values', () => {
- const { result } = renderHook(() => useVerseSelection(), { wrapper });
-
- expect(result.current.selectedVerseUsfms).toBeInstanceOf(Set);
- expect(result.current.selectedVerseUsfms.size).toBe(0);
- expect(result.current.selectedCount).toBe(0);
- expect(typeof result.current.toggleVerse).toBe('function');
- expect(typeof result.current.isSelected).toBe('function');
- expect(typeof result.current.clearSelection).toBe('function');
- });
-});
diff --git a/packages/hooks/src/useVerseSelection.ts b/packages/hooks/src/useVerseSelection.ts
index 64b334ee..edf3fb87 100644
--- a/packages/hooks/src/useVerseSelection.ts
+++ b/packages/hooks/src/useVerseSelection.ts
@@ -4,6 +4,9 @@ import {
type VerseSelectionContextData,
} from './context/VerseSelectionContext';
+/**
+ * @deprecated No replacement needed. Remove usage. Will be removed in the next major version.
+ */
export function useVerseSelection(): VerseSelectionContextData {
const context = useContext(VerseSelectionContext);
if (!context) {
diff --git a/packages/hooks/vitest.config.ts b/packages/hooks/vitest.config.ts
index 89b8a63d..64a97bfb 100644
--- a/packages/hooks/vitest.config.ts
+++ b/packages/hooks/vitest.config.ts
@@ -16,6 +16,15 @@ export default defineConfig({
reportsDirectory: './coverage',
all: true,
include: ['src/**/*.{ts,tsx}'],
+ exclude: [
+ 'src/useInitData.ts',
+ 'src/useChapterNavigation.ts',
+ 'src/useVerseSelection.ts',
+ 'src/context/ReaderContext.tsx',
+ 'src/context/ReaderProvider.tsx',
+ 'src/context/VerseSelectionContext.tsx',
+ 'src/context/VerseSelectionProvider.tsx',
+ ],
},
},
});