Amelioration a11y des labels de champs dans Conversation (#2282)

* feat: Amélioration de la lecture d'écran divers inputs

* feat: Améliore le label lu par le SR > InputSuggestion

* feat: Fix autofocus

* fix: Corrige label

* fix: nettoyage

* fix: Modifie selecteurs d'input
pull/2237/head
Benjamin Arias 2022-09-15 10:23:53 +02:00 committed by GitHub
parent cf69b0c356
commit 75e5aef14d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 76 additions and 35 deletions

View File

@ -3,42 +3,49 @@ import { fr } from '../../../support/utils'
type cyType = typeof cy
type Obj = Record<string, { test: (cy: cyType) => unknown; path: string }[]>
const coutTotalSelector = 'input[id="contrat salarié . prix du travail"]'
const salaireBrutSelector =
'input[id="contrat salarié . rémunération . brut de base"]'
const salaireNetSelector = 'input[id="contrat salarié . rémunération . net"]'
const salaireNetApresImpot =
'input[id="contrat salarié . rémunération . net après impôt"]'
describe('Test prerender', function () {
const testSimuSalaire = (cy: cyType) => {
cy.contains('Mensuel')
cy.contains('Annuel')
cy.contains('Coût total')
cy.get('input[title="Coût total"]').should('exist')
cy.get(coutTotalSelector).should('exist')
cy.contains('Salaire brut')
cy.get('input[title="Salaire brut"]').should('exist')
cy.get(salaireBrutSelector).should('exist')
cy.contains('salaire médian')
cy.contains('SMIC')
cy.contains('Salaire net')
cy.get('input[title="Salaire net"]').should('exist')
cy.get(salaireNetSelector).should('exist')
cy.contains('Salaire net après impôt')
cy.get('input[title="Salaire net après impôt"]').should('exist')
cy.get(salaireNetApresImpot).should('exist')
}
const testSimuSalary = () => {
cy.contains('Labor cost')
cy.get('input[title="Labor cost"]').should('exist')
cy.get(coutTotalSelector).should('exist')
cy.contains('Gross salary')
cy.get('input[title="Gross salary"]').should('exist')
cy.get(salaireBrutSelector).should('exist')
cy.contains('median earnings')
cy.contains('SMIC')
cy.contains('Net salary')
cy.get('input[title="Net salary"]').should('exist')
cy.get(salaireNetSelector).should('exist')
cy.contains('Net salary after income tax')
cy.get('input[title="Net salary after income tax"]').should('exist')
cy.get(salaireNetApresImpot).should('exist')
}
const tests = {
@ -56,16 +63,18 @@ describe('Test prerender', function () {
cy.contains('Annuel')
cy.contains("Chiffre d'affaires")
cy.get('input[title="Chiffre d\'affaires"]').should('exist')
cy.get('input[id="entreprise . chiffre d\'affaires"]').should('exist')
cy.contains('Charges')
cy.get('input[id="entreprise . charges"]').should('exist')
cy.contains('Revenu net')
cy.get('input[title="Revenu net"]').should('exist')
cy.get('input[id="dirigeant . rémunération . nette"]').should('exist')
cy.contains('Revenu après impôt')
cy.get('input[title="Revenu après impôt"]').should('exist')
cy.get(
'input[id="dirigeant . rémunération . nette après impôt"]'
).should('exist')
},
path: '/simulateurs/indépendant',
},

View File

@ -40,11 +40,15 @@ export function SimulationGoals({
publique={publique}
role="group"
id="simulator-legend"
aria-labelledby="simulator-legend"
aria-labelledby="simulator-legend-label"
aria-live="polite"
>
<ThemeProvider theme={(theme) => ({ ...theme, darkMode: true })}>
<div className="sr-only" aria-hidden="true" id="simulator-legend">
<div
className="sr-only"
aria-hidden="true"
id="simulator-legend-label"
>
{legend}
</div>
{children}

View File

@ -81,6 +81,8 @@ export function MultipleAnswerInput<Names extends string = DottedName>({
onSelectionChange={handleChange}
defaultSelectedKey={defaultValue}
selectedKey={currentSelection}
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={props.autoFocus}
>
{choice.children.map((node) => (
<Item
@ -98,16 +100,17 @@ export function MultipleAnswerInput<Names extends string = DottedName>({
<RadioCardGroup
onChange={handleChange}
value={currentSelection ?? undefined}
aria-labelledby={props['aria-labelledby'] || undefined}
>
{choice.children.map((node) => (
<Fragment key={node.dottedName}>
<RadioCard
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={
props.autoFocus &&
defaultValue ===
`'${relativeDottedName(props.dottedName, node.dottedName)}'`
`'${relativeDottedName(props.dottedName, node.dottedName)}'`
}
aria-labelledby={props['aria-labelledby'] || undefined}
value={`'${relativeDottedName(
props.dottedName,
node.dottedName
@ -132,7 +135,8 @@ export function MultipleAnswerInput<Names extends string = DottedName>({
>
<RadioChoice
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={props.autoFocus ? defaultValue : undefined}
autoFocus={props.autoFocus}
defaultValue={defaultValue}
choice={choice}
rootDottedName={props.dottedName}
type={type}
@ -143,12 +147,14 @@ export function MultipleAnswerInput<Names extends string = DottedName>({
function RadioChoice<Names extends string = DottedName>({
choice,
defaultValue,
autoFocus,
rootDottedName,
type,
}: {
choice: Choice
autoFocus?: string
defaultValue?: string
autoFocus?: boolean
rootDottedName: Names
type: 'radio' | 'toggle'
}) {
@ -165,7 +171,7 @@ function RadioChoice<Names extends string = DottedName>({
) ? null : 'children' in node ? (
<div
role="group"
aria-describedby={node.dottedName + '-legend'}
aria-labelledby={node.dottedName + '-legend'}
css={`
margin-top: -1rem;
`}
@ -174,6 +180,9 @@ function RadioChoice<Names extends string = DottedName>({
<Spacing lg />
<StyledSubRadioGroup>
<RadioChoice
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={autoFocus}
defaultValue={defaultValue}
choice={node}
rootDottedName={rootDottedName}
type={type}
@ -185,8 +194,16 @@ function RadioChoice<Names extends string = DottedName>({
<Radio
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={
autoFocus ===
`'${relativeDottedName(rootDottedName, node.dottedName)}'`
// Doit autoFocus si correspond à la valeur par défaut
(defaultValue &&
defaultValue ===
`'${relativeDottedName(
rootDottedName,
node.dottedName
)}'` &&
autoFocus) ||
// Sinon doit autoFocus automatiquement
autoFocus
}
value={`'${relativeDottedName(
rootDottedName,
@ -200,6 +217,7 @@ function RadioChoice<Names extends string = DottedName>({
<ExplicableRule
light
dottedName={node.dottedName as DottedName}
aria-label={`En savoir plus sur ${node.title}`}
/>
)}
</span>

View File

@ -73,11 +73,6 @@ export default function Conversation({
)
}
const questionLabel = evaluateQuestion(
engine,
engine.getRule(currentQuestion)
)
return (
<>
<div className="print-only">
@ -107,9 +102,13 @@ export default function Conversation({
align-items: baseline;
`}
>
<H3>
{questionLabel}
<ExplicableRule light dottedName={currentQuestion} />
<H3 id="questionHeader">
{evaluateQuestion(engine, engine.getRule(currentQuestion))}
<ExplicableRule
aria-label="En savoir plus"
light
dottedName={currentQuestion}
/>
</H3>
</div>
<fieldset>
@ -118,7 +117,7 @@ export default function Conversation({
onChange={onChange}
key={currentQuestion}
onSubmit={goToNextQuestion}
aria-label={questionLabel}
aria-labelledby="questionHeader"
/>
</fieldset>
<Spacing md />

View File

@ -10,6 +10,7 @@ export function ExplicableRule<Names extends string = DottedName>({
dottedName,
light,
bigPopover,
...props
}: {
dottedName: Names
light?: boolean
@ -37,6 +38,7 @@ export function ExplicableRule<Names extends string = DottedName>({
light={light}
bigPopover={bigPopover}
className="print-hidden"
{...props}
>
<Markdown>{rule.rawNode.description}</Markdown>
{rule.rawNode.références && (

View File

@ -38,7 +38,7 @@ export default function InputSuggestions({
onSecondClick && onSecondClick(value)
}
}}
title={t('cliquez pour insérer cette suggestion')}
aria-label={`${t('Insérer la suggestion')} : ${text}`}
>
{text}
</Link>

View File

@ -159,6 +159,10 @@ export default function RuleInput<Names extends string = DottedName>({
)
}
// Pas de title sur NumberInput pour avoir une bonne expérience avec
// lecteur d'écran
delete commonProps.title
return (
<NumberInput
{...commonProps}

View File

@ -63,6 +63,7 @@ function SelectComponent({
<TextField
type="search"
placeholder={t("Saisissez votre domaine d'activité")}
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={autoFocus}
errorMessage={
searchResults &&
@ -161,7 +162,5 @@ export default function SelectAtmp(
)
}, [])
if (!options) return null
return <SelectComponent {...props} options={options} />
return <SelectComponent {...props} options={options || []} />
}

View File

@ -20,11 +20,17 @@ export default function ButtonHelp({
light,
bigPopover,
className,
...props
}: ButtonHelpProps) {
return (
<PopoverWithTrigger
trigger={(buttonProps) => (
<StyledButton $light={light} className={className} {...buttonProps}>
<StyledButton
$light={light}
className={className}
{...buttonProps}
{...props}
>
<CircleIcon
aria-hidden="true"
width="24"

View File

@ -446,7 +446,7 @@ checklist:
open: Hide details
cible: target
clickexample: Click on a situation to see the result
cliquez pour insérer cette suggestion: click to insert this suggestion
Insérer la suggestion: Insert the suggestion
comparaisonRégimes:
ACRE: <0>ACRE</0><1>1 year <1>(automatic and unconditional)</1></1><2>Between 3
and 4 quarters <2>(subject to eligibility requirements)</2></2>