diff --git a/.changeset/biblecard-single-error-alert.md b/.changeset/biblecard-single-error-alert.md
new file mode 100644
index 00000000..7f05a1a5
--- /dev/null
+++ b/.changeset/biblecard-single-error-alert.md
@@ -0,0 +1,7 @@
+---
+'@youversion/platform-react-ui': patch
+---
+
+Fix the `BibleCard` error state announcing two alerts, and keep the version picker usable while an error is showing. The "Error" label stays in the header slot but drops its `role="alert"` and `aria-live`, leaving the message block in the card body as the only alert region. The picker no longer disappears on error, so a 404 has an in-card fix: switch to a version that carries the passage.
+
+The shared message block also drops a redundant `aria-live` and hides its icon with `aria-hidden`, so `VerseOfTheDay` and standalone `BibleTextView` pick up the same accessibility fixes. Their visible text is unchanged, and neither gains an "Error" label. The eight status-aware messages, their six locales, and how errors are derived are untouched.
diff --git a/packages/ui/src/components/bible-card.stories.tsx b/packages/ui/src/components/bible-card.stories.tsx
index 52649147..ef2a9ad9 100644
--- a/packages/ui/src/components/bible-card.stories.tsx
+++ b/packages/ui/src/components/bible-card.stories.tsx
@@ -186,6 +186,7 @@ export const Error: Story = {
args: {
reference: 'LUK.1.39-45',
versionId: 111,
+ showVersionPicker: true,
},
tags: ['integration'],
parameters: {
@@ -206,12 +207,26 @@ export const Error: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
+ // The header slot carries the "Error" label; the body block is the one alert.
await waitFor(async () => {
await expect(canvas.getByRole('heading', { level: 2, name: /error/i })).toBeInTheDocument();
- const errorMessages = canvas.getAllByText(
- 'The Bible service is having trouble right now. Please try again in a moment.',
- );
- await expect(errorMessages.length).toBeGreaterThan(0);
+ });
+
+ const alerts = canvas.getAllByRole('alert');
+
+ await expect(alerts).toHaveLength(1);
+ await expect(alerts[0]).toHaveTextContent(
+ 'The Bible service is having trouble right now. Please try again in a moment.',
+ );
+
+ // The picker is the in-card recovery path: a 404 is fixed by switching versions.
+ const versionPickerButton = await canvas.findByRole('button', {
+ name: /change bible version/i,
+ });
+
+ await waitFor(async () => {
+ await expect(versionPickerButton).toBeEnabled();
+ await expect(versionPickerButton).toHaveTextContent(/NIV/i);
});
},
};
diff --git a/packages/ui/src/components/bible-card.test.tsx b/packages/ui/src/components/bible-card.test.tsx
index 3f58a99e..b48b398a 100644
--- a/packages/ui/src/components/bible-card.test.tsx
+++ b/packages/ui/src/components/bible-card.test.tsx
@@ -6,8 +6,17 @@ import { render, act, within, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { BibleCard } from './bible-card';
import type { FootnoteData } from './verse';
-import { usePassage, useVersion, useTheme } from '@youversion/platform-react-hooks';
-import type { BiblePassage, BibleVersion } from '@youversion/platform-core';
+import {
+ useFilteredVersions,
+ useLanguage,
+ useLanguages,
+ useOrganizations,
+ usePassage,
+ useTheme,
+ useVersion,
+ useVersions,
+} from '@youversion/platform-react-hooks';
+import type { BiblePassage, BibleVersion, Language } from '@youversion/platform-core';
vi.mock('@youversion/platform-react-hooks');
@@ -156,6 +165,90 @@ describe('BibleCard - Delayed spinner', () => {
});
});
+describe('BibleCard - Error state', () => {
+ function createError(message: string, status?: number): Error {
+ return Object.assign(new Error(message), status === undefined ? {} : { status });
+ }
+
+ beforeEach(() => {
+ vi.mocked(useTheme).mockReturnValue('light');
+ vi.mocked(useVersion).mockReturnValue({
+ version: mockVersion,
+ loading: false,
+ error: null,
+ refetch: vi.fn(),
+ });
+ vi.mocked(usePassage).mockReturnValue({
+ passage: null,
+ loading: false,
+ error: createError('Request failed with status 503', 503),
+ refetch: vi.fn(),
+ });
+ });
+
+ it('should render exactly one alert region', () => {
+ const { container } = render(
{message}