Skip to content

Commit

Permalink
chore(forms): enhance fields with aria attributes (#3182)
Browse files Browse the repository at this point in the history
  • Loading branch information
tujoworker authored Jan 22, 2024
1 parent eff0caf commit bc05141
Show file tree
Hide file tree
Showing 30 changed files with 1,121 additions and 212 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ breadcrumb:
- Ensure you have a submit button. Use the [Form.SubmitButton](/uilib/extensions/forms/extended-features/Form/SubmitButton/) for that.
- Ensure to let browser autofill personal data if applicable, based on HTML [autocomplete attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete). By using the `path` property with semantic names (e.g. `path="/firstName"`), browser will be able to provide a correct autofill integration.
- In some cases, it is appreciated to temporary store user entered input data. Use the `sessionStorageId` feature provided by [Form.Handler](/uilib/extensions/forms/extended-features/Form/Handler/) for that.
- Required fields should have `aria-required="true"` attribute. Use the `required` property for that.
- Fields with errors should have `aria-invalid="true"` attribute.

```jsx
<Form.Handler autoComplete={true}>
<Field.String path="/firstName" />
<Field.Email path="/email" />
<Field.String path="/firstName" required />
<Field.Email path="/email" required />
<Form.SubmitButton>Submit</Form.SubmitButton>
</Form.Handler>
```
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const { value } = useDataValue(componentProps)

Advanced usage:

```ts
```tsx
const {
// Return Parameters:
value,
Expand All @@ -25,17 +25,24 @@ const {
hasError,
isChanged,
setHasFocus,
ariaAttributes,

// Component Properties
...rest
} = useDataValue({
// Your Component Properties:
// Your component props + internal props such as "required":
...componentProps,

// Internal Properties, listed down below:
required,
errorMessages,
})

render(
<Input
value={value}
onChange={onChange}
onFocus={onFocus}
onBlur={onBlur}
{...ariaAttributes}
/>,
)
```

### Internal Properties
Expand Down Expand Up @@ -71,6 +78,7 @@ It returns all of the given component properties, in addition to these:
- `dataContext` the internal shared data context.
- `error` the error object, in case an error is invoked. Will skip returning the error object, if the hook is used in a nested [FieldBlock](/uilib/extensions/forms/create-component/FieldBlock/).
- `hasError` will return true in case of an error, even if the hook is nested in a `FieldBlock`.
- `ariaAttributes` object that include needed aria attributes, ready to be spread on form elements.
- `isChanged` returns `true` if the value has changed with e.g. `handleChange`.
- `setHasFocus` accepts a boolean as value. When called, it will update the internal logic - for event handling and validation. Will re-render the React Hook and its outer component.
- `onFocus` event handler to assign to a form element.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,27 @@ describe('Field.BankAccountNumber', () => {
expect(input).toHaveAttribute('inputmode', 'numeric')
})

it('should validate with ARIA rules', async () => {
const result = render(<Field.BankAccountNumber value="12345678" />)
describe('ARIA', () => {
it('should validate with ARIA rules', async () => {
const result = render(
<Field.BankAccountNumber required validateInitially />
)

expect(await axeComponent(result)).toHaveNoViolations()
expect(await axeComponent(result)).toHaveNoViolations()
})

it('should have aria-required', () => {
render(<Field.BankAccountNumber required />)

const input = document.querySelector('input')
expect(input).toHaveAttribute('aria-required', 'true')
})

it('should have aria-invalid', () => {
render(<Field.BankAccountNumber required validateInitially />)

const input = document.querySelector('input')
expect(input).toHaveAttribute('aria-invalid', 'true')
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -86,18 +86,40 @@ describe('Field.Boolean', () => {
expect(screen.queryByRole('alert')).not.toBeInTheDocument()
})

it('should validate with ARIA rules', async () => {
const result = render(
<Field.Boolean
label="Label"
variant="checkbox"
value={false}
validateInitially
required
/>
)
describe('ARIA', () => {
it('should validate with ARIA rules', async () => {
const result = render(
<Field.Boolean
label="Label"
variant="checkbox"
validateInitially
required
/>
)

expect(await axeComponent(result)).toHaveNoViolations()
})

it('should have aria-required', () => {
render(<Field.Boolean label="Label" variant="checkbox" required />)

expect(await axeComponent(result)).toHaveNoViolations()
const input = document.querySelector('input')
expect(input).toHaveAttribute('aria-required', 'true')
})

it('should have aria-invalid', () => {
render(
<Field.Boolean
label="Label"
variant="checkbox"
validateInitially
required
/>
)

const input = document.querySelector('input')
expect(input).toHaveAttribute('aria-invalid', 'true')
})
})
})

Expand Down Expand Up @@ -161,18 +183,47 @@ describe('Field.Boolean', () => {
expect(screen.queryByRole('alert')).not.toBeInTheDocument()
})

it('should validate with ARIA rules', async () => {
const result = render(
<Field.Boolean
label="Label"
variant="button"
value={false}
validateInitially
required
/>
)
describe('ARIA', () => {
it('should validate with ARIA rules', async () => {
const result = render(
<Field.Boolean
label="Label"
variant="button"
validateInitially
required
/>
)

expect(
await axeComponent(result, {
rules: {
// Because of aria-required is not allowed on buttons – but VO still reads it
'aria-allowed-attr': { enabled: false },
},
})
).toHaveNoViolations()
})

it('should have aria-required', () => {
render(<Field.Boolean label="Label" variant="button" required />)

expect(await axeComponent(result)).toHaveNoViolations()
const button = document.querySelector('button')
expect(button).toHaveAttribute('aria-required', 'true')
})

it('should have aria-invalid', () => {
render(
<Field.Boolean
label="Label"
variant="button"
validateInitially
required
/>
)

const button = document.querySelector('button')
expect(button).toHaveAttribute('aria-invalid', 'true')
})
})
})

Expand Down Expand Up @@ -244,18 +295,53 @@ describe('Field.Boolean', () => {
expect(screen.queryByRole('alert')).not.toBeInTheDocument()
})

it('should validate with ARIA rules', async () => {
const result = render(
<Field.Boolean
label="Label"
variant="checkbox-button"
value={false}
validateInitially
required
/>
)
describe('ARIA', () => {
it('should validate with ARIA rules', async () => {
const result = render(
<Field.Boolean
label="Label"
variant="checkbox-button"
validateInitially
required
/>
)

expect(
await axeComponent(result, {
rules: {
// Because of aria-required is not allowed on buttons – but VO still reads it
'aria-allowed-attr': { enabled: false },
},
})
).toHaveNoViolations()
})

it('should have aria-required', () => {
render(
<Field.Boolean
label="Label"
variant="checkbox-button"
required
/>
)

const button = document.querySelector('button')
expect(button).toHaveAttribute('aria-required', 'true')
})

expect(await axeComponent(result)).toHaveNoViolations()
it('should have aria-invalid', () => {
render(
<Field.Boolean
label="Label"
variant="checkbox-button"
validateInitially
required
/>
)

const button = document.querySelector('button')
expect(button).toHaveAttribute('aria-invalid', 'true')
})
})
})

Expand Down Expand Up @@ -342,18 +428,53 @@ describe('Field.Boolean', () => {
expect(screen.queryByRole('alert')).not.toBeInTheDocument()
})

it('should validate with ARIA rules', async () => {
const result = render(
<Field.Boolean
label="Label"
variant="buttons"
value={false}
validateInitially
required
/>
)
describe('ARIA', () => {
it('should validate with ARIA rules', async () => {
const result = render(
<Field.Boolean
label="Label"
variant="buttons"
validateInitially
required
/>
)

expect(
await axeComponent(result, {
rules: {
// Because of aria-required is not allowed on buttons – but VO still reads it
'aria-allowed-attr': { enabled: false },
},
})
).toHaveNoViolations()
})

it('should have aria-required', () => {
render(<Field.Boolean label="Label" variant="buttons" required />)

expect(await axeComponent(result)).toHaveNoViolations()
const [first, second] = Array.from(
document.querySelectorAll('button')
)
expect(first).toHaveAttribute('aria-required', 'true')
expect(second).toHaveAttribute('aria-required', 'true')
})

it('should have aria-invalid', () => {
render(
<Field.Boolean
label="Label"
variant="buttons"
required
validateInitially
/>
)

const [first, second] = Array.from(
document.querySelectorAll('button')
)
expect(first).toHaveAttribute('aria-invalid', 'true')
expect(second).toHaveAttribute('aria-invalid', 'true')
})
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,27 @@ describe('Field.Currency', () => {
expect(input).toHaveAttribute('inputmode', 'decimal')
})

it('should validate with ARIA rules', async () => {
const result = render(<Field.Currency label="Label" value={123} />)
describe('ARIA', () => {
it('should validate with ARIA rules', async () => {
const result = render(
<Field.Currency label="Label" required validateInitially />
)

expect(await axeComponent(result)).toHaveNoViolations()
expect(await axeComponent(result)).toHaveNoViolations()
})

it('should have aria-required', () => {
render(<Field.Currency required />)

const input = document.querySelector('input')
expect(input).toHaveAttribute('aria-required', 'true')
})

it('should have aria-invalid', () => {
render(<Field.Currency required validateInitially />)

const input = document.querySelector('input')
expect(input).toHaveAttribute('aria-invalid', 'true')
})
})
})
2 changes: 2 additions & 0 deletions packages/dnb-eufemia/src/extensions/forms/Field/Date/Date.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ function DateComponent(props: Props) {
error,
hasError,
disabled,
ariaAttributes,
handleFocus,
handleBlur,
handleChange,
Expand Down Expand Up @@ -101,6 +102,7 @@ function DateComponent(props: Props) {
on_reset={handleChange}
onFocus={handleFocus}
onBlur={handleBlur}
{...ariaAttributes}
{...pickSpacingProps(props)}
/>
</FieldBlock>
Expand Down
Loading

0 comments on commit bc05141

Please sign in to comment.