Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/course-home/courseware-search/CoursewareSearch.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,13 @@ const CoursewareSearchModal = ({ ...sectionProps }) => {
<div className="courseware-search__outer-content">
<div className="courseware-search__content" data-testid="courseware-search-content">
<div className="courseware-search__form">
<h1 className="h2">{formatMessage(messages.searchModuleTitle)}</h1>
<h1 className="h2" id="courseware-search-title">{formatMessage(messages.searchModuleTitle)}</h1>
<CoursewareSearchForm
searchTerm={searchKeyword}
onSubmit={handleSubmit}
onChange={handleOnChange}
placeholder={formatMessage(messages.searchBarPlaceholderText)}
labelledBy="courseware-search-title"
/>
<div className="courseware-search__close">
<Button
Expand Down
6 changes: 6 additions & 0 deletions src/course-home/courseware-search/CoursewareSearch.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ describe('CoursewareSearch', () => {
const section = screen.getByTestId('courseware-search-dialog');
expect(section.style.getPropertyValue('--modal-top-position')).toBe(`${tabsTopPosition}px`);
});

it('should expose the visible heading as the search input\'s accessible name', async () => {
renderComponent();
const input = await screen.findByRole('searchbox', { name: /search this course/i });
expect(input).toBeInTheDocument();
});
});

describe('when clicking on the "Close" button', () => {
Expand Down
7 changes: 5 additions & 2 deletions src/course-home/courseware-search/CoursewareSearchForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const CoursewareSearchForm = ({
onSubmit,
onChange,
placeholder,
labelledBy,
}) => {
const { formatMessage } = useIntl();

Expand All @@ -25,8 +26,8 @@ const CoursewareSearchForm = ({
}}
>
<div className="pgn__searchfield_wrapper" data-testid="courseware-search-form">
<SearchField.Label />
<SearchField.Input placeholder={placeholder} autoFocus />
{/* The visible "Search this course" heading is the accessible label via aria-labelledby. */}
<SearchField.Input placeholder={placeholder} aria-labelledby={labelledBy} autoFocus />
<SearchField.ClearButton />
</div>
<SearchField.SubmitButton
Expand All @@ -43,13 +44,15 @@ CoursewareSearchForm.propTypes = {
onSubmit: PropTypes.func,
onChange: PropTypes.func,
placeholder: PropTypes.string,
labelledBy: PropTypes.string,
};

CoursewareSearchForm.defaultProps = {
searchTerm: undefined,
onSubmit: undefined,
onChange: undefined,
placeholder: undefined,
labelledBy: undefined,
};

export default CoursewareSearchForm;
16 changes: 15 additions & 1 deletion src/course-home/courseware-search/CoursewareSearchForm.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import {
} from '../../setupTest';
import CoursewareSearchForm from './CoursewareSearchForm';

function renderComponent(placeholder, onSubmit, onChange) {
function renderComponent(placeholder, onSubmit, onChange, labelledBy) {
const { container } = render(<CoursewareSearchForm
placeholder={placeholder}
onSubmit={onSubmit}
onChange={onChange}
labelledBy={labelledBy}
/>);
return container;
}
Expand Down Expand Up @@ -54,6 +55,19 @@ describe('CoursewareSearchToggle', () => {
});
});

it('should associate the input with an external label via aria-labelledby', async () => {
await act(async () => renderComponent(placeholderText, onSubmitHandlerMock, onChangeHandlerMock, 'external-id'));
const input = await screen.findByRole('searchbox');
expect(input).toHaveAttribute('aria-labelledby', 'external-id');
});

it('should not render a visually-hidden duplicate "Search" label', async () => {
let container;
await act(async () => { container = renderComponent(placeholderText, onSubmitHandlerMock, onChangeHandlerMock, 'external-id'); });
// Guards against re-introducing <SearchField.Label />, which renders an sr-only duplicate label.
expect(container.querySelector('label.sr-only, label .sr-only')).toBeNull();
});

afterEach(() => {
jest.clearAllMocks();
});
Expand Down
4 changes: 2 additions & 2 deletions src/course-home/courseware-search/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ const messages = defineMessages({
},
searchBarPlaceholderText: {
id: 'learn.coursewareSearch.searchBarPlaceholderText',
defaultMessage: 'Search',
description: 'Placeholder text for the Courseware Search input control',
defaultMessage: 'Find topics across this course',
description: 'Placeholder hint shown inside the Courseware Search input describing what can be searched.',
},
Comment on lines 34 to 38

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to change the placeholder text here? Having "Find topics across this course" as placeholder text feels odd to me, and this would require new translations.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The suggestion from a11y internal team was: If placeholder text is used, it should provide a helpful example or hint rather than repeat the accessible label. 'Find topics across this course' was AI generated but I am open to hear options for placeholder text.

loading: {
id: 'learn.coursewareSearch.loading',
Expand Down