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'inputpull/2237/head
parent
cf69b0c356
commit
75e5aef14d
|
@ -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',
|
||||
},
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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 && (
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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 || []} />
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue