Skip to content

Commit

Permalink
Merge pull request #1037 from US-Trustee-Program/CAMS-437-case-detail…
Browse files Browse the repository at this point in the history
…s-accessibility

CAMS-437 case details accessibility
  • Loading branch information
fmaddenflx authored Nov 15, 2024
2 parents 7eb3735 + 82c6d2c commit 0920f3e
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 64 deletions.
14 changes: 10 additions & 4 deletions user-interface/src/case-detail/CaseDetailScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ export default function CaseDetailScreen(props: CaseDetailProps) {
const [navState, setNavState] = useState<number>(mapNavState(location.pathname));
const [selectedDateRange, setSelectedDateRange] = useState<DateRange>({});
const [documentRange, setDocumentRange] = useState<DocumentRange>({ first: 0, last: 0 });
const [documentNumberError, setDocumentNumberError] = useState<boolean>(false);
const findInDocketRef = useRef<InputRef>(null);
const findByDocketNumberRef = useRef<InputRef>(null);
const dateRangeRef = useRef<DateRangePickerRef>(null);
Expand Down Expand Up @@ -279,8 +280,10 @@ export default function CaseDetailScreen(props: CaseDetailProps) {
const newDocumentNumber = parseInt(ev.target.value.trim());
if (isNaN(newDocumentNumber)) {
setDocumentNumber(null);
setDocumentNumberError(ev.target.value.trim().length !== 0);
return;
}
setDocumentNumberError(false);
setDocumentNumber(newDocumentNumber);
}

Expand Down Expand Up @@ -461,6 +464,7 @@ export default function CaseDetailScreen(props: CaseDetailProps) {
aria-label="Find text in Docket entries. Results will be updated while you type."
aria-live="polite"
icon="search"
position="right"
autoComplete="off"
onChange={searchDocketText}
ref={findInDocketRef}
Expand Down Expand Up @@ -499,17 +503,19 @@ export default function CaseDetailScreen(props: CaseDetailProps) {
<div className="in-docket-search form-field" data-testid="docket-number-search">
<div className="usa-search usa-search--small">
<Input
pattern="^[0-9]*$"
inputMode="numeric"
id="document-number-search-field"
title="Enter numbers only"
className="search-icon"
id="document-number-search-field"
type="number"
type="text"
errorMessage={
documentNumberError === true ? 'Please enter a number.' : undefined
}
name="search-by-document-number"
label="Go to Document Number"
aria-label="Go to specific Document Number. Results will be updated while you type."
aria-live="polite"
icon="search"
position="right"
autoComplete="off"
onChange={searchDocumentNumber}
min={documentRange.first}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,37 @@ describe('court docket panel tests', () => {
);
});

test('should not render docket number if no docket number exists and aria-label should only be present if there is a docket number', () => {
const document1Number = docketEntries[firstIndex].documentNumber;
const document3Number = docketEntries[firstIndex + 2].documentNumber;
render(
<BrowserRouter>
<CaseDetailCourtDocket
caseId="081-12-12345"
docketEntries={docketEntries}
searchString=""
hasDocketEntries={true}
isDocketLoading={false}
/>
</BrowserRouter>,
);

const docketEntry1 = screen.getByTestId('docket-entry-0');
const docketEntry2 = screen.getByTestId('docket-entry-1');
const docketEntry3 = screen.getByTestId('docket-entry-2');
expect(docketEntry1).toBeInTheDocument();
expect(docketEntry2).toBeInTheDocument();
expect(docketEntry3).toBeInTheDocument();

const document1NumberColumn = docketEntry1.querySelector('.document-number-column');
const document2NumberColumn = docketEntry2.querySelector('.document-number-column');
const document3NumberColumn = docketEntry3.querySelector('.document-number-column');

expect(document1NumberColumn).toHaveAttribute('aria-label', `Docket Number ${document1Number}`);
expect(document2NumberColumn).not.toHaveAttribute('aria-label');
expect(document3NumberColumn).toHaveAttribute('aria-label', `Docket Number ${document3Number}`);
});

describe('No docket entry alert tests', () => {
test('should display alert when no docket entries are found', async () => {
render(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,11 @@ export default function CaseDetailCourtDocket(props: CaseDetailCourtDocketProps)
<div className="grid-row">
<div
className="grid-col-1 document-number-column"
aria-label={`Docket Number ${docketEntry.documentNumber}`}
aria-label={
docketEntry.documentNumber
? `Docket Number ${docketEntry.documentNumber}`
: undefined
}
>
{docketEntry.documentNumber}
</div>
Expand Down
39 changes: 21 additions & 18 deletions user-interface/src/case-detail/panels/CaseDetailOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,24 +110,27 @@ export default function CaseDetailOverview(props: CaseDetailOverviewProps) {
Region {caseDetail.regionId.replace(/^0*/, '')} - {caseDetail.officeName} Office
</div>
)}
<ul className="usa-list usa-list--unstyled">
{caseDetail.assignments &&
caseDetail.assignments.length > 0 &&
(caseDetail.assignments as Array<AttorneyUser>)?.map(
(staff: AttorneyUser, idx: number) => {
return (
<li key={idx} className="individual-assignee">
<span className="assignee-name">{staff.name}</span>
<span className="vertical-divider"> | </span>
<span className="assignee-role">Trial Attorney</span>
</li>
);
},
)}
{caseDetail.assignments?.length == 0 && (
<span className="unassigned-placeholder">(unassigned)</span>
)}
</ul>
{(typeof caseDetail.assignments === 'undefined' ||
caseDetail.assignments.length === 0) && (
<span className="unassigned-placeholder">(unassigned)</span>
)}
{caseDetail.assignments && caseDetail.assignments.length > 0 && (
<ul className="usa-list usa-list--unstyled">
{caseDetail.assignments &&
caseDetail.assignments.length > 0 &&
(caseDetail.assignments as Array<AttorneyUser>)?.map(
(staff: AttorneyUser, idx: number) => {
return (
<li key={idx} className="individual-assignee">
<span className="assignee-name">{staff.name}</span>
<span className="vertical-divider"> | </span>
<span className="assignee-role">Trial Attorney</span>
</li>
);
},
)}
</ul>
)}
</div>
</div>
<div className="judge-information padding-bottom-4 case-card">
Expand Down
43 changes: 42 additions & 1 deletion user-interface/src/lib/components/uswds/Input.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,35 @@ describe('Tests for USWDS Input component.', () => {
});
});

describe('Test error handling', () => {
test('Should have error attributes set properly when an error occurs', async () => {
const errorMessageId = 'input-1-input__error-message';
const { rerender } = render(
<div>
<Input id="input-1" errorMessage={undefined}></Input>
</div>,
);

const inputEl = screen.getByTestId('input-1');
expect(inputEl).not.toHaveAttribute('aria-invalid');
expect(inputEl).not.toHaveAttribute('aria-errorMessage');

rerender(
<div>
<Input id="input-1" errorMessage="TEST MESSAGE"></Input>
</div>,
);

await waitFor(() => {
expect(inputEl).toHaveAttribute('aria-invalid', 'true');
expect(inputEl).toHaveAttribute('aria-errorMessage', errorMessageId);
});

const errorMessageDiv = document.getElementById(errorMessageId);
expect(errorMessageDiv).toHaveTextContent('TEST MESSAGE');
});
});

describe('Tests for USWDS Input component when no value is initially set.', () => {
const ref = React.createRef<InputRef>();

Expand Down Expand Up @@ -96,7 +125,18 @@ describe('Tests for USWDS Input component when no value is initially set.', () =
const inputEl = screen.getByTestId('input-1');
expect(inputEl).toHaveClass(expectedClassName);
});
test('should have a default classes if not className provided', () => {

test('should have data-position="left" if position is set to "left"', () => {
render(
<div>
<Input id="input-1" position="left"></Input>
</div>,
);
const inputEl = screen.getByTestId('input-1');
expect(inputEl).toHaveAttribute('data-position', 'left');
});

test('should have a default classes and data-position="right" if neither className nor position are provided', () => {
render(
<div>
<Input id="input-1"></Input>
Expand All @@ -105,6 +145,7 @@ describe('Tests for USWDS Input component when no value is initially set.', () =
const inputEl = screen.getByTestId('input-1');
expect(inputEl).toHaveClass('usa-input');
expect(inputEl).toHaveClass('usa-tooltip');
expect(inputEl).toHaveAttribute('data-position', 'right');
});
});
});
89 changes: 49 additions & 40 deletions user-interface/src/lib/components/uswds/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ import { InputRef } from '../../type-declarations/input-fields';
import Icon from './Icon';
import Button, { UswdsButtonStyle } from './Button';

// Alias for readability.
//const debounce = setTimeout;

export type InputProps = Omit<JSX.IntrinsicElements['input'], 'onFocus'> & {
label?: string;
autoComplete?: 'off';
Expand All @@ -16,6 +13,7 @@ export type InputProps = Omit<JSX.IntrinsicElements['input'], 'onFocus'> & {
includeClearButton?: boolean;
ariaDescription?: string;
onFocus?: (ev: React.FocusEvent<HTMLElement>) => void;
errorMessage?: string;
};

function InputComponent(props: InputProps, ref: React.Ref<InputRef>) {
Expand Down Expand Up @@ -81,48 +79,59 @@ function InputComponent(props: InputProps, ref: React.Ref<InputRef>) {
useImperativeHandle(ref, () => ({ clearValue, resetValue, setValue, getValue, disable }));

return (
<div className="usa-form-group">
<label className="usa-label" id={props.id + '-label'} htmlFor={props.id}>
{props.label}
{props.required && <span className="required-form-field">{' *'}</span>}
</label>
{ariaDescription && (
<div className="usa-hint" id={ariaDescribedBy()}>
{ariaDescription}
</div>
)}
<div className="usa-input-group">
<input
{...otherProps}
className={`usa-input usa-tooltip ${props.className ?? ''}`}
data-position={props.position ?? 'right'}
onChange={handleOnChange}
onFocus={handleFocus}
data-testid={props.id}
disabled={inputDisabled}
value={inputValue}
aria-describedby={ariaDescription ? ariaDescribedBy() : undefined}
ref={inputRef}
/>
{includeClearButton && !inputDisabled && (
<div className="usa-input-suffix" aria-hidden="true">
<Button
id={`button-clear-${props.id}`}
uswdsStyle={UswdsButtonStyle.Unstyled}
onClick={clearValue}
aria-label="clear text input."
>
<Icon name="close"></Icon>
</Button>
<>
<div className="usa-form-group">
<label className="usa-label" id={props.id + '-label'} htmlFor={props.id}>
{props.label}
{props.required && <span className="required-form-field">{' *'}</span>}
</label>
{ariaDescription && (
<div className="usa-hint" id={ariaDescribedBy()}>
{ariaDescription}
</div>
)}
{!includeClearButton && props.icon && (
<div className="usa-input-prefix" aria-hidden="true">
<Icon focusable={false} name={props.icon}></Icon>
<div
className={`usa-input-group ${props.errorMessage && props.errorMessage.length > 0 ? 'usa-input-group--error' : ''}`}
>
<input
{...otherProps}
className={`usa-input usa-tooltip ${props.className ?? ''}`}
aria-invalid={props.errorMessage ? 'true' : undefined}
aria-errormessage={props.errorMessage ? `${props.id}-input__error-message` : undefined}
data-position={props.position ?? 'right'}
onChange={handleOnChange}
onFocus={handleFocus}
data-testid={props.id}
disabled={inputDisabled}
value={inputValue}
aria-describedby={ariaDescription ? ariaDescribedBy() : undefined}
ref={inputRef}
/>
{includeClearButton && !inputDisabled && (
<div className="usa-input-suffix" aria-hidden="true">
<Button
id={`button-clear-${props.id}`}
uswdsStyle={UswdsButtonStyle.Unstyled}
onClick={clearValue}
aria-label="clear text input."
>
<Icon name="close"></Icon>
</Button>
</div>
)}
{!includeClearButton && props.icon && (
<div className="usa-input-prefix" aria-hidden="true">
<Icon focusable={false} name={props.icon}></Icon>
</div>
)}
</div>
{props.errorMessage && props.errorMessage.length > 0 && (
<div id={`${props.id}-input__error-message`} className="usa-input__error-message">
{props.errorMessage}
</div>
)}
</div>
</div>
</>
);
}

Expand Down
24 changes: 24 additions & 0 deletions user-interface/src/lib/components/uswds/forms.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
$secondary-dark: #B50909;
$warning-light: #FFFFCA;
$warning-text: #990000;

button.unstyled-button {
margin-left: 1rem;
Expand Down Expand Up @@ -29,6 +31,28 @@ button.unstyled-button {
.usa-input-suffix {
z-index: 1;
}
.usa-input[data-position=right] + .usa-input-prefix {
position: absolute;
right: 0;
}
.usa-input[data-position=left] {
padding-right: 0;
padding-left: 2.5rem;
}
.usa-input[data-position=left] + .usa-input-prefix {
position: absolute;
left: 0;
right: auto;
}
}
.usa-input-group.usa-input-group--error {
.usa-input {
background-color: $warning-light;
}
}
.usa-input__error-message {
padding: 0.25rem 0;
color: $warning-text;
}

.usa-radio__input + label {
Expand Down

0 comments on commit 0920f3e

Please sign in to comment.