diff --git a/frontend/src/components/Chat/ConverterPanel/ConverterParams.test.tsx b/frontend/src/components/Chat/ConverterPanel/ConverterParams.test.tsx new file mode 100644 index 0000000000..065e773bfc --- /dev/null +++ b/frontend/src/components/Chat/ConverterPanel/ConverterParams.test.tsx @@ -0,0 +1,46 @@ +import React from 'react' +import { render, screen } from '@testing-library/react' +import { FluentProvider, webLightTheme } from '@fluentui/react-components' +import type { ConverterCatalogEntry } from '../../../types' +import ConverterParams from './ConverterParams' + +const converter: ConverterCatalogEntry = { + converter_type: 'TestConverter', + supported_input_types: ['text'], + supported_output_types: ['text'], + is_llm_based: false, + parameters: [ + { name: 'key', type_name: 'str', required: true, description: 'Encryption key' }, + { name: 'append_description', type_name: 'bool', required: false, default: 'false' }, + { name: 'mode', type_name: 'str', required: false, choices: ['fast', 'safe'] }, + { name: 'file_path', type_name: 'str', required: false, description: 'Input file path' }, + ], +} + +function renderParams() { + return render( + + + , + ) +} + +describe('ConverterParams accessibility', () => { + it('associates visible parameter names with every control type', () => { + renderParams() + + expect(screen.getByRole('textbox', { name: /key/i })).toBeInTheDocument() + expect(screen.getByRole('switch', { name: /append_description/i })).toBeInTheDocument() + expect(screen.getByRole('combobox', { name: /mode/i })).toBeInTheDocument() + expect(screen.getByRole('textbox', { name: /file_path/i })).toBeInTheDocument() + expect(screen.getByRole('button', { name: /browse for file_path/i })).toBeInTheDocument() + }) +}) diff --git a/frontend/src/components/Chat/ConverterPanel/ConverterParams.tsx b/frontend/src/components/Chat/ConverterPanel/ConverterParams.tsx index fbccf69a4c..9f039b7305 100644 --- a/frontend/src/components/Chat/ConverterPanel/ConverterParams.tsx +++ b/frontend/src/components/Chat/ConverterPanel/ConverterParams.tsx @@ -7,14 +7,18 @@ interface ParamInputProps { param: Parameter value: string isMissing: boolean + labelId: string + descriptionId?: string onChange: (name: string, value: string) => void } -function ConverterParameterChoiceViewer({ param, value, onChange }: ParamInputProps) { +function ConverterParameterChoiceViewer({ param, value, labelId, descriptionId, onChange }: ParamInputProps) { const stringDefault = typeof param.default === 'string' ? param.default : '' return ( onChange(param.name, data.value)} className={isMissing ? styles.paramInputError : undefined} data-testid={`param-${param.name}`} @@ -43,6 +49,7 @@ function ParameterFileViewer({ param, value, isMissing, onChange, onBrowse }: Pa {paramsExpanded && (converter.parameters ?? []).map((param) => { const isMissing = showValidation && param.required && !paramValues[param.name]?.trim() + const labelId = `converter-param-${param.name}-label` + const descriptionId = param.description ? `converter-param-${param.name}-description` : undefined return (
- + {param.name}{param.required ? ' *' : ''} {param.description && ( - + )} {param.type_name === 'bool' ? ( onParamChange(param.name, data.checked ? 'true' : 'false')} label={(paramValues[param.name] ?? (typeof param.default === 'string' ? param.default : 'false')).toLowerCase() === 'true' ? 'True' : 'False'} data-testid={`param-${param.name}`} /> ) : param.choices ? ( - + ) : /path|file/i.test(param.name) || /path|file/i.test(param.description ?? '') ? ( - + ) : ( - + )} {isMissing && ( Required