diff --git a/.changeset/tricky-keys-train.md b/.changeset/tricky-keys-train.md new file mode 100644 index 00000000..51903b34 --- /dev/null +++ b/.changeset/tricky-keys-train.md @@ -0,0 +1,7 @@ +--- +'@youversion/platform-react-hooks': patch +'@youversion/platform-core': patch +'@youversion/platform-react-ui': patch +--- + +fix: use spinner icon instead of "Loading..." text in Bible version button diff --git a/packages/ui/src/components/bible-reader.stories.tsx b/packages/ui/src/components/bible-reader.stories.tsx index b88a3f49..015e7e48 100644 --- a/packages/ui/src/components/bible-reader.stories.tsx +++ b/packages/ui/src/components/bible-reader.stories.tsx @@ -1,8 +1,10 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { expect, fn, screen, spyOn, userEvent, waitFor } from 'storybook/test'; +import { http, HttpResponse, delay } from 'msw'; import { BibleReader } from './bible-reader'; import { setupAuthenticatedUser } from '../test/utils'; import { INTER_FONT, SOURCE_SERIF_FONT } from '@/lib/verse-html-utils'; +import mockBibles from '../test/mock-data/bibles.json'; let signInMock: ReturnType; @@ -630,7 +632,7 @@ export const WithoutAuth: Story = { const chapterButton = screen.getByRole('button', { name: /change bible book and chapter/i }); await expect(chapterButton).toBeInTheDocument(); - const versionButton = screen.getByRole('button', { name: /change bible version/i }); + const versionButton = await screen.findByRole('button', { name: /bible version/i }); await expect(versionButton).toBeInTheDocument(); // Settings should still work @@ -639,6 +641,68 @@ export const WithoutAuth: Story = { }, }; +/** + * Tests that the Bible version button in the toolbar shows a loading spinner + * initially and then transitions to showing the version abbreviation once loaded. + */ +export const VersionButtonLoadingStates: Story = { + tags: ['integration'], + args: { + defaultVersionId: 111, + book: 'JHN', + chapter: '1', + }, + parameters: { + msw: { + handlers: [ + // Delay the version endpoint so the loading state is reliably observable + http.get('*/v1/bibles/:id', async ({ params }) => { + await delay(1000); + const id = params.id as string; + const bible = + mockBibles.individual[id as keyof typeof mockBibles.individual] ?? + mockBibles.collections.default.data.find((b) => b.id === Number(id)); + if (bible) return HttpResponse.json(bible); + return new HttpResponse(null, { status: 404 }); + }), + ], + }, + }, + render: (args) => ( +
+ + + + +
+ ), + play: async () => { + // The version button should exist in the toolbar (label varies by loading state) + const versionButton = screen.getByRole('button', { name: /bible version/i }); + await expect(versionButton).toBeInTheDocument(); + + // The delayed MSW handler guarantees the loading state is visible + const spinner = versionButton.querySelector('[role="status"]'); + await expect(spinner).toBeInTheDocument(); + await expect(versionButton).toBeDisabled(); + await expect(versionButton).toHaveAttribute('aria-label', 'Loading Bible version'); + + // After loading completes, the button should show the version abbreviation (e.g. "NIV") + await waitFor( + async () => { + await expect(versionButton).not.toBeDisabled(); + // Spinner should be gone + await expect(versionButton.querySelector('[role="status"]')).not.toBeInTheDocument(); + // aria-label should switch to "Change" once loaded + await expect(versionButton).toHaveAttribute('aria-label', 'Change Bible version'); + // Should display the abbreviation text + await expect(versionButton.textContent).toMatch(/[A-Z]{2,}/); + }, + { timeout: 5000 }, + ); + }, +}; + /** * Tests that a rich intro chapter (Joshua) renders correctly with real-world content * including structured sections (At a Glance, Purpose, Major Themes), italic spans, diff --git a/packages/ui/src/components/bible-reader.tsx b/packages/ui/src/components/bible-reader.tsx index ccd1431f..b6ed276f 100644 --- a/packages/ui/src/components/bible-reader.tsx +++ b/packages/ui/src/components/bible-reader.tsx @@ -434,8 +434,16 @@ function Toolbar({ border = 'top' }: { border?: 'top' | 'bottom' }) { variant="secondary" className="yv:font-bold yv:text-foreground" disabled={loading} + aria-label={loading ? 'Loading Bible version' : 'Change Bible version'} > - {loading ? 'Loading...' : version?.localized_abbreviation || 'Select version'} + {/* This div exists merely as a wrapper to minimize width layout shifting */} +
+ {loading ? ( + + ) : ( + version?.localized_abbreviation || 'Select version' + )} +
)}