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
5 changes: 5 additions & 0 deletions .changeset/sort-bible-versions-alphabetically.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@youversion/platform-react-hooks': patch
---

Sort Bible versions alphabetically in BibleVersionPicker after language, search, and recent-version filters. Uses stable English locale collation for consistent ordering across environments.
111 changes: 106 additions & 5 deletions packages/hooks/src/useFilteredVersions.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,36 @@ const mockVersions: BibleVersion[] = [
},
];

const sortedMockVersions: BibleVersion[] = [
mockVersions[0]!,
mockVersions[4]!,
mockVersions[1]!,
mockVersions[3]!,
mockVersions[2]!,
];

const shuffledMockVersions: BibleVersion[] = [
mockVersions[4]!,
mockVersions[2]!,
mockVersions[0]!,
mockVersions[3]!,
mockVersions[1]!,
];

function expectAlphabeticalOrder(versions: BibleVersion[]): void {
const titles = versions.map((v) => v.localized_title || v.title);
const sortedTitles = [...titles].sort((a, b) =>
a.localeCompare(b, undefined, { sensitivity: 'base' }),
);
expect(titles).toEqual(sortedTitles);
}

describe('useFilteredVersions', () => {
describe('language filtering', () => {
it('should return all versions when language is "*"', () => {
const { result } = renderHook(() => useFilteredVersions(mockVersions, '', '*'));

expect(result.current).toEqual(mockVersions);
expect(result.current).toEqual(sortedMockVersions);
expect(result.current).toHaveLength(5);
});

Expand Down Expand Up @@ -117,13 +141,13 @@ describe('useFilteredVersions', () => {
it('should return all versions when search term is empty', () => {
const { result } = renderHook(() => useFilteredVersions(mockVersions, '', '*'));

expect(result.current).toEqual(mockVersions);
expect(result.current).toEqual(sortedMockVersions);
});

it('should return all versions when search term is whitespace only', () => {
const { result } = renderHook(() => useFilteredVersions(mockVersions, ' ', '*'));

expect(result.current).toEqual(mockVersions);
expect(result.current).toEqual(sortedMockVersions);
});

it('should filter by title', () => {
Expand Down Expand Up @@ -202,7 +226,7 @@ describe('useFilteredVersions', () => {
it('should not exclude any versions when recentVersions is undefined', () => {
const { result } = renderHook(() => useFilteredVersions(mockVersions, '', '*', undefined));

expect(result.current).toEqual(mockVersions);
expect(result.current).toEqual(sortedMockVersions);
expect(result.current).toHaveLength(5);
});

Expand Down Expand Up @@ -237,7 +261,7 @@ describe('useFilteredVersions', () => {
it('should not exclude any versions when recentVersions is empty array', () => {
const { result } = renderHook(() => useFilteredVersions(mockVersions, '', '*', []));

expect(result.current).toEqual(mockVersions);
expect(result.current).toEqual(sortedMockVersions);
expect(result.current).toHaveLength(5);
});

Expand All @@ -256,6 +280,83 @@ describe('useFilteredVersions', () => {
});
});

describe('alphabetical sorting', () => {
it('should sort all versions alphabetically regardless of input order', () => {
const { result } = renderHook(() => useFilteredVersions(shuffledMockVersions, '', '*'));

expect(result.current).toEqual(sortedMockVersions);
expectAlphabeticalOrder(result.current);
});

it('should sort search results alphabetically', () => {
const { result } = renderHook(() =>
useFilteredVersions(shuffledMockVersions, 'Version', '*'),
);

expect(result.current).toHaveLength(2);
expect(result.current[0]?.title).toBe('King James Version');
expect(result.current[1]?.title).toBe('New International Version');
expectAlphabeticalOrder(result.current);
});

it('should sort remaining versions alphabetically after excluding recent versions', () => {
const recentVersions = [
{ id: 1, title: 'King James Version', localized_abbreviation: 'KJV' },
];

const { result } = renderHook(() =>
useFilteredVersions(shuffledMockVersions, '', '*', recentVersions),
);

expect(result.current).toHaveLength(4);
expect(result.current.find((v) => v.id === 1)).toBeUndefined();
expect(result.current).toEqual(sortedMockVersions.filter((v) => v.id !== 1));
expectAlphabeticalOrder(result.current);
});

it('should sort by localized_title when it differs from title', () => {
const versions: BibleVersion[] = [
{
...mockVersions[0]!,
id: 10,
title: 'Zulu Title',
localized_title: 'Alpha Localized',
},
{
...mockVersions[1]!,
id: 11,
title: 'Alpha Title',
localized_title: 'Zulu Localized',
},
];

const { result } = renderHook(() => useFilteredVersions(versions, '', '*'));

expect(result.current.map((v) => v.id)).toEqual([10, 11]);
});

it('should fall back to title when localized_title is missing', () => {
const versions: BibleVersion[] = [
{
...mockVersions[0]!,
id: 20,
title: 'Beta Title',
localized_title: undefined as unknown as string,
},
{
...mockVersions[1]!,
id: 21,
title: 'Alpha Title',
localized_title: undefined as unknown as string,
},
];

const { result } = renderHook(() => useFilteredVersions(versions, '', '*'));

expect(result.current.map((v) => v.id)).toEqual([21, 20]);
});
});

describe('memoization', () => {
it('should return the same reference when inputs do not change', () => {
const { result, rerender } = renderHook(
Expand Down
13 changes: 13 additions & 0 deletions packages/hooks/src/useFilteredVersions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ import type { BibleVersion } from '@youversion/platform-core';
import { useMemo } from 'react';
import { getISOFromVersion } from './utility/version';

function getVersionSortTitle(version: BibleVersion): string {
return version.localized_title || version.title;
}

function compareVersionsAlphabetically(a: BibleVersion, b: BibleVersion): number {
const byTitle = getVersionSortTitle(a).localeCompare(getVersionSortTitle(b), 'en', {
sensitivity: 'base',
});
return byTitle !== 0 ? byTitle : a.id - b.id;
}
Comment thread
Dustin-Kelley marked this conversation as resolved.

/**
* Custom hook to filter versions based on search term
*/
Expand Down Expand Up @@ -40,6 +51,8 @@ export function useFilteredVersions(
result = result.filter((version) => !recentVersionIds.includes(version.id));
}

result.sort(compareVersionsAlphabetically);
Comment thread
Dustin-Kelley marked this conversation as resolved.

return result;
}, [versions, recentVersions, searchTerm, selectedLanguage]);
}
Loading