diff --git a/src/components/ui/FieldRenderer.test.tsx b/src/components/ui/FieldRenderer.test.tsx new file mode 100644 index 0000000..be5d6b2 --- /dev/null +++ b/src/components/ui/FieldRenderer.test.tsx @@ -0,0 +1,81 @@ +// @vitest-environment jsdom +import { describe, it, expect, vi } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { FieldRenderer } from './FieldRenderer'; +import type { UseFormRegister } from 'react-hook-form'; + +function makeRegister(): UseFormRegister { + return vi.fn((name: string) => ({ + name, + ref: vi.fn(), + onChange: vi.fn(), + onBlur: vi.fn(), + })) as unknown as UseFormRegister; +} + +const textFieldWithTooltip = { + key: 'bucket_name', + label: 'Bucket Name', + inputType: 'text' as const, + tooltipText: 'The name of your storage bucket', +}; + +const selectFieldWithTooltip = { + key: 'region', + label: 'Region', + inputType: 'select' as const, + tooltipText: 'Select your deployment region', + options: [ + { value: 'us-east', label: 'US East' }, + { value: 'eu-west', label: 'EU West' }, + ], +}; + +describe('FieldRenderer - DEBT-01 aria-label consistency', () => { + it('Test 1: text-branch tooltip button has aria-label="More info about {field.label}"', () => { + render( + + ); + const tooltipBtn = screen.getByRole('button', { name: /more info about bucket name/i }); + expect(tooltipBtn).toBeDefined(); + expect(tooltipBtn.getAttribute('aria-label')).toBe('More info about Bucket Name'); + }); + + it('Test 2: select-branch tooltip button has aria-label="More info about {field.label}"', () => { + render( + + ); + const tooltipBtn = screen.getByRole('button', { name: /more info about region/i }); + expect(tooltipBtn).toBeDefined(); + expect(tooltipBtn.getAttribute('aria-label')).toBe('More info about Region'); + }); + + it('Test 3: text-branch tooltip button is findable via getByRole with name pattern', () => { + render( + + ); + // This query pattern must work for both branches + const btn = screen.getByRole('button', { name: /more info about/i }); + expect(btn).toBeDefined(); + }); + + it('Test 3b: select-branch tooltip button is findable via getByRole with name pattern', () => { + render( + + ); + const btn = screen.getByRole('button', { name: /more info about/i }); + expect(btn).toBeDefined(); + }); +}); diff --git a/src/components/ui/FieldRenderer.tsx b/src/components/ui/FieldRenderer.tsx index bea8e8d..1f9c095 100644 --- a/src/components/ui/FieldRenderer.tsx +++ b/src/components/ui/FieldRenderer.tsx @@ -91,10 +91,10 @@ export function FieldRenderer({ field, register, error }: FieldRendererProps) { )} diff --git a/src/components/ui/PasswordField.tsx b/src/components/ui/PasswordField.tsx index 1d0e2d7..d1cbd5d 100644 --- a/src/components/ui/PasswordField.tsx +++ b/src/components/ui/PasswordField.tsx @@ -24,10 +24,10 @@ export function PasswordField({ id, label, error, registration, placeholder, hel )} diff --git a/src/components/wizard/RemoteConfigStep.test.tsx b/src/components/wizard/RemoteConfigStep.test.tsx index 5af2e1e..6dcb723 100644 --- a/src/components/wizard/RemoteConfigStep.test.tsx +++ b/src/components/wizard/RemoteConfigStep.test.tsx @@ -40,7 +40,7 @@ describe('RemoteConfigStep', () => { it('shows SAS URL field by default (default auth method)', async () => { renderWithBackend('azureblob'); await waitFor(() => { - expect(screen.getByLabelText(/sas url/i)).toBeDefined(); + expect(screen.getByLabelText(/sas url/i, { selector: 'input' })).toBeDefined(); }); }); @@ -52,9 +52,9 @@ describe('RemoteConfigStep', () => { fireEvent.click(screen.getByText(/access key/i, { selector: 'button' })); await waitFor(() => { // After switching to Access Key, the SAS URL wrapper should be hidden - const sasInput = screen.getByLabelText(/sas url/i); + const sasInput = screen.getByLabelText(/sas url/i, { selector: 'input' }); const sasWrapper = sasInput.closest('div.hidden'); - const keyInput = screen.getByLabelText(/access key/i); + const keyInput = screen.getByLabelText(/access key/i, { selector: 'input' }); const keyWrapper = keyInput.closest('div.block'); expect(sasWrapper).toBeDefined(); expect(keyWrapper).toBeDefined(); @@ -67,11 +67,11 @@ describe('RemoteConfigStep', () => { // Wait for form to render await waitFor(() => { - expect(screen.getByLabelText(/sas url/i)).toBeDefined(); + expect(screen.getByLabelText(/sas url/i, { selector: 'input' })).toBeDefined(); }); // Type a value into the SAS URL field - const sasInput = screen.getByLabelText(/sas url/i); + const sasInput = screen.getByLabelText(/sas url/i, { selector: 'input' }); await user.type(sasInput, 'https://mystorage.blob.core.windows.net/?sv=test'); // Switch to Access Key @@ -82,7 +82,7 @@ describe('RemoteConfigStep', () => { // The value should still be there await waitFor(() => { - const input = screen.getByLabelText(/sas url/i) as HTMLInputElement; + const input = screen.getByLabelText(/sas url/i, { selector: 'input' }) as HTMLInputElement; expect(input.value).toBe('https://mystorage.blob.core.windows.net/?sv=test'); }); }); @@ -126,7 +126,7 @@ describe('RemoteConfigStep', () => { it('renders OAuth Token field', async () => { renderWithBackend('onedrive' as BackendType); await waitFor(() => { - expect(screen.getByLabelText(/oauth token/i)).toBeDefined(); + expect(screen.getByLabelText(/oauth token/i, { selector: 'input' })).toBeDefined(); }); }); it('renders Drive ID field', async () => { @@ -271,7 +271,7 @@ describe('RemoteConfigStep', () => { }); await user.type(screen.getByLabelText(/storage account name/i), 'mystorageaccount'); // Fill SAS URL field so form can submit without other required-field errors - await user.type(screen.getByLabelText(/sas url/i), 'https://mystorage.blob.core.windows.net/?sv=test'); + await user.type(screen.getByLabelText(/sas url/i, { selector: 'input' }), 'https://mystorage.blob.core.windows.net/?sv=test'); await user.click(screen.getByRole('button', { name: /next/i })); await waitFor(() => { expect(screen.queryByText(/3.{1,5}24 lowercase alphanumeric/i)).toBeNull(); @@ -343,9 +343,8 @@ describe('RemoteConfigStep', () => { it('renders ⓘ button on azureblob sas_url field', async () => { renderWithBackend('azureblob'); await waitFor(() => { - expect(screen.getByLabelText(/sas url/i)).toBeDefined(); + expect(screen.getByLabelText(/sas url/i, { selector: 'input' })).toBeDefined(); }); - // ⓘ button does not exist yet — will throw "Unable to find role 'button'" expect(screen.getByRole('button', { name: /more info about sas url/i })).toBeDefined(); }); @@ -353,9 +352,8 @@ describe('RemoteConfigStep', () => { const user = userEvent.setup(); renderWithBackend('azureblob'); await waitFor(() => { - expect(screen.getByLabelText(/sas url/i)).toBeDefined(); + expect(screen.getByLabelText(/sas url/i, { selector: 'input' })).toBeDefined(); }); - // ⓘ button does not exist yet — getByRole will throw await user.click(screen.getByRole('button', { name: /more info about sas url/i })); await waitFor(() => { // Tooltip text should be visible after clicking ⓘ @@ -367,9 +365,8 @@ describe('RemoteConfigStep', () => { const user = userEvent.setup(); renderWithBackend('azureblob'); await waitFor(() => { - expect(screen.getByLabelText(/sas url/i)).toBeDefined(); + expect(screen.getByLabelText(/sas url/i, { selector: 'input' })).toBeDefined(); }); - // ⓘ button does not exist yet — getByRole will throw await user.click(screen.getByRole('button', { name: /more info about sas url/i })); await waitFor(() => { expect(screen.getByText(/shared access signature/i)).toBeDefined(); @@ -392,9 +389,8 @@ describe('RemoteConfigStep', () => { it('renders ⓘ button on OneDrive token field', async () => { renderWithBackend('onedrive' as BackendType); await waitFor(() => { - expect(screen.getByLabelText(/oauth token/i)).toBeDefined(); + expect(screen.getByLabelText(/oauth token/i, { selector: 'input' })).toBeDefined(); }); - // ⓘ button does not exist yet — will throw "Unable to find role 'button'" // Note: this matches any "more info about X" button on the OneDrive form expect(screen.getByRole('button', { name: /more info about oauth token/i })).toBeDefined(); });