From 11108561c048e3c86957b1628c84e943a25b603e Mon Sep 17 00:00:00 2001 From: Amp Date: Tue, 6 Oct 2026 18:20:46 +0000 Subject: [PATCH 1/4] fix(ui): fall back to promotional attribution Amp-Thread-ID: https://ampcode.com/threads/T-01a11267-f18f-7245-9247-87604d8328ae --- .changeset/fuzzy-lions-attribute.md | 5 ++ .../ui/src/components/bible-card.test.tsx | 60 ++++++++++++++++++- packages/ui/src/components/bible-card.tsx | 18 +++++- 3 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 .changeset/fuzzy-lions-attribute.md diff --git a/.changeset/fuzzy-lions-attribute.md b/.changeset/fuzzy-lions-attribute.md new file mode 100644 index 00000000..e2dfdd5a --- /dev/null +++ b/.changeset/fuzzy-lions-attribute.md @@ -0,0 +1,5 @@ +--- +'@youversion/platform-react-ui': patch +--- + +Fall back to Bible version promotional content in BibleCard when copyright is blank. diff --git a/packages/ui/src/components/bible-card.test.tsx b/packages/ui/src/components/bible-card.test.tsx index bf3a76e4..3e59a31a 100644 --- a/packages/ui/src/components/bible-card.test.tsx +++ b/packages/ui/src/components/bible-card.test.tsx @@ -37,9 +37,9 @@ const mockVersion: BibleVersion = { youversion_deep_link: 'https://bible.com/versions/3034', }; -function idleVersion(): UseVersionResult { +function idleVersion(version: BibleVersion = mockVersion): UseVersionResult { return { - version: mockVersion, + version, loading: false, error: null, refetch: () => undefined, @@ -66,6 +66,7 @@ function renderCard( onVersionChange?: (id: number) => void; maxWidth?: number | '100%'; hostWidth?: number; + version?: BibleVersion; } = {}, ) { const { @@ -76,11 +77,12 @@ function renderCard( onVersionChange, maxWidth, hostWidth, + version = mockVersion, } = extra; const card = ( idleVersion(), + useVersion: () => idleVersion(version), usePassage: () => passage, }} > @@ -112,6 +114,58 @@ const multiVersePassage: BiblePassage = { }; const highlights: Highlight[] = [{ version_id: 111, passage_id: 'JHN.1.2', color: YELLOW }]; +describe('BibleCard - attribution', () => { + const loaded = passageResult({ passage: mockPassage, loading: false }); + + it('prefers non-whitespace copyright over promotional content', () => { + const { container } = renderCard(loaded, { + version: { + ...mockVersion, + copyright: 'Copyright attribution', + promotional_content: 'Promotional attribution', + }, + }); + + expect(within(container).getByText('Copyright attribution')).toBeInTheDocument(); + expect(within(container).queryByText('Promotional attribution')).toBeNull(); + }); + + it.each([null, undefined, '', ' \n '])( + 'falls back to promotional content when copyright is %s', + (copyright) => { + const { container } = renderCard(loaded, { + version: { + ...mockVersion, + copyright, + promotional_content: 'Promotional attribution', + }, + }); + + expect( + within(container).getByText('Promotional attribution'), + ).toBeInTheDocument(); + expect(container.querySelector('strong')).toBeNull(); + }, + ); + + it.each([ + { state: 'absent', version: mockVersion }, + { + state: 'blank', + version: { ...mockVersion, copyright: ' ', promotional_content: '\n' }, + }, + ])('renders no attribution when both fields are $state', ({ version }) => { + const { container } = renderCard(loaded, { + version, + }); + + const attribution = requireHtmlElement( + container.querySelector('section > div > div:last-child p bdi'), + ); + expect(attribution).toHaveTextContent(/^\s*$/); + }); +}); + describe('BibleCard - Delayed spinner', () => { beforeEach(() => { vi.useFakeTimers(); diff --git a/packages/ui/src/components/bible-card.tsx b/packages/ui/src/components/bible-card.tsx index 858db154..6bf6b168 100644 --- a/packages/ui/src/components/bible-card.tsx +++ b/packages/ui/src/components/bible-card.tsx @@ -143,11 +143,20 @@ function BibleCardVersionPicker({ ); } -function BibleCardFooter({ copyright }: { copyright?: string | null }): React.ReactNode { +function BibleCardFooter({ + copyright, + promotionalContent, +}: { + copyright?: string | null; + promotionalContent?: string | null; +}): React.ReactNode { + const attribution = + (copyright?.trim() && copyright) || (promotionalContent?.trim() && promotionalContent) || ''; + return (

- {copyright || ''} + {attribution}

@@ -275,7 +284,10 @@ export function BibleCard({ /> - +
); From 1a9b7d23bbde3fbf91bae8520df83e4a8b277650 Mon Sep 17 00:00:00 2001 From: Amp Date: Tue, 6 Oct 2026 18:31:57 +0000 Subject: [PATCH 2/4] fix(ui): add BibleReader attribution fallback Amp-Thread-ID: https://ampcode.com/threads/T-01a11267-f18f-7245-9247-87604d8328ae --- .changeset/fuzzy-lions-attribute.md | 2 +- packages/ui/src/components/bible-card.tsx | 19 ++------ .../bible-reader-controlled.test.tsx | 44 +++++++++++++++++++ packages/ui/src/components/bible-reader.tsx | 11 +++-- .../ui/src/lib/bible-version-attribution.ts | 9 ++++ 5 files changed, 65 insertions(+), 20 deletions(-) create mode 100644 packages/ui/src/lib/bible-version-attribution.ts diff --git a/.changeset/fuzzy-lions-attribute.md b/.changeset/fuzzy-lions-attribute.md index e2dfdd5a..353f8371 100644 --- a/.changeset/fuzzy-lions-attribute.md +++ b/.changeset/fuzzy-lions-attribute.md @@ -2,4 +2,4 @@ '@youversion/platform-react-ui': patch --- -Fall back to Bible version promotional content in BibleCard when copyright is blank. +Fall back to Bible version promotional content in BibleCard and BibleReader when copyright is blank. diff --git a/packages/ui/src/components/bible-card.tsx b/packages/ui/src/components/bible-card.tsx index 6bf6b168..9c862920 100644 --- a/packages/ui/src/components/bible-card.tsx +++ b/packages/ui/src/components/bible-card.tsx @@ -20,6 +20,7 @@ import { LoaderIcon } from './icons/loader'; import { AnimatedHeight } from './animated-height'; import { useInterfaceDirection } from '@/lib/direction'; import { useResolvedScriptureDirection } from '@/lib/scripture-direction'; +import { getBibleVersionAttribution } from '@/lib/bible-version-attribution'; type PassageResult = ReturnType; type VersionResult = ReturnType; @@ -143,20 +144,11 @@ function BibleCardVersionPicker({ ); } -function BibleCardFooter({ - copyright, - promotionalContent, -}: { - copyright?: string | null; - promotionalContent?: string | null; -}): React.ReactNode { - const attribution = - (copyright?.trim() && copyright) || (promotionalContent?.trim() && promotionalContent) || ''; - +function BibleCardFooter({ attribution }: { attribution?: string | null }): React.ReactNode { return (

- {attribution} + {attribution || ''}

@@ -284,10 +276,7 @@ export function BibleCard({ /> - +
); diff --git a/packages/ui/src/components/bible-reader-controlled.test.tsx b/packages/ui/src/components/bible-reader-controlled.test.tsx index 8111d4e4..27cbfdca 100644 --- a/packages/ui/src/components/bible-reader-controlled.test.tsx +++ b/packages/ui/src/components/bible-reader-controlled.test.tsx @@ -193,6 +193,50 @@ function renderReader(props: Partial = {}, overrides?: Hoo return render(wrapReader(props, overrides)); } +it.each([ + { + state: 'copyright and promotional content are populated', + copyright: 'Copyright attribution', + promotionalContent: 'Promotional attribution', + expected: 'Copyright attribution', + }, + { + state: 'copyright is blank', + copyright: ' \n ', + promotionalContent: 'Promotional attribution', + expected: 'Promotional attribution', + }, +])( + 'renders the selected version attribution when $state', + ({ copyright, promotionalContent, expected }) => { + const { container } = renderReader( + {}, + { + ...defaultOverrides(), + useVersion: () => ({ + version: { + ...mockVersion, + copyright, + promotional_content: promotionalContent, + }, + loading: false, + error: null, + refetch: () => undefined, + }), + }, + ); + + expect(screen.getByText(expected)).toBeInTheDocument(); + expect(container.querySelector('footer strong')).toBeNull(); + }, +); + +it('renders no attribution footer when both fields are absent', () => { + const { container } = renderReader(); + + expect(container.querySelector('footer')).toBeNull(); +}); + function getVerseEl(container: HTMLElement, verse: number): HTMLElement { const els = container.querySelectorAll(`.yv-v[v="${verse}"]`); const el = els[els.length - 1]; diff --git a/packages/ui/src/components/bible-reader.tsx b/packages/ui/src/components/bible-reader.tsx index 65dcb95f..7d328064 100644 --- a/packages/ui/src/components/bible-reader.tsx +++ b/packages/ui/src/components/bible-reader.tsx @@ -63,6 +63,7 @@ import { YouVersionPlatformConfiguration } from '@youversion/platform-core'; import { BibleReaderSearch, type BibleReaderSearchPressData } from './bible-reader-search'; import { useTransientVerseFocus, type VerseFocusRequest } from '@/lib/use-transient-verse-focus'; import { BibleReaderNavigation } from './bible-reader-navigation'; +import { getBibleVersionAttribution } from '@/lib/bible-version-attribution'; type BibleReaderContextType = { book: string; @@ -801,6 +802,8 @@ function Content() { scriptureDirection, } = useBibleReaderContext(); const { version } = useVersion(versionId); + const attribution = getBibleVersionAttribution(version); + const publisherUrl = version?.publisher_url; const bookData = useMemo(() => { return booksData.find((b) => b.id === book); @@ -1281,18 +1284,18 @@ function Content() {
)} - {version?.copyright && ( + {attribution && (
); diff --git a/packages/ui/src/components/bible-reader.tsx b/packages/ui/src/components/bible-reader.tsx index 7d328064..c074b518 100644 --- a/packages/ui/src/components/bible-reader.tsx +++ b/packages/ui/src/components/bible-reader.tsx @@ -59,11 +59,10 @@ import { BibleTextView, getCleanVerseText, type FootnoteData } from './verse'; import { buildVerseReference, buildVerseShareText, joinVerseTexts } from '@/lib/verse-share'; import { isHighlightsLive } from '@/lib/feature-flags'; import { YvComponentStyles } from '@/lib/yv-styles-components'; -import { YouVersionPlatformConfiguration } from '@youversion/platform-core'; +import { YouVersionPlatformConfiguration, getPassageAttribution } from '@youversion/platform-core'; import { BibleReaderSearch, type BibleReaderSearchPressData } from './bible-reader-search'; import { useTransientVerseFocus, type VerseFocusRequest } from '@/lib/use-transient-verse-focus'; import { BibleReaderNavigation } from './bible-reader-navigation'; -import { getBibleVersionAttribution } from '@/lib/bible-version-attribution'; type BibleReaderContextType = { book: string; @@ -802,7 +801,7 @@ function Content() { scriptureDirection, } = useBibleReaderContext(); const { version } = useVersion(versionId); - const attribution = getBibleVersionAttribution(version); + const attribution = version ? getPassageAttribution(version) : null; const publisherUrl = version?.publisher_url; const bookData = useMemo(() => { @@ -1290,7 +1289,7 @@ function Content() { style={{ fontSize: currentFontSize }} >

- {attribution} + {attribution.text}

{publisherUrl ? (