Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Numeric Input] - Link tooltip content to input field for assistive technologies #1891

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
5 changes: 5 additions & 0 deletions .changeset/odd-moons-remember.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/perseus": patch
---

[Numeric Input] - Associate format options tooltip content with input field for assistive technologies
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ exports[`server item renderer should snapshot: initial render 1`] = `
<span>
<div>
<input
aria-describedby="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
aria-invalid="false"
aria-label="Your answer:"
autocapitalize="off"
Expand All @@ -51,12 +52,17 @@ exports[`server item renderer should snapshot: initial render 1`] = `
type="text"
value=""
/>
<span
id="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
style="display: none;"
/>
</div>
<div
style="position: relative; height: 0px; display: none;"
>
<div
class="tooltipContainer"
role="tooltip"
style="position: absolute; left: 0px;"
>
<div
Expand Down
23 changes: 21 additions & 2 deletions packages/perseus/src/components/input-with-examples.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,21 @@ class InputWithExamples extends React.Component<Props, State> {

_renderInput: () => any = () => {
const id = this._getUniqueId();
const ariaId = `aria-for-${id}`;
// Generate text from a known set of format options that will read well in a screen reader
const examplesAria =
this.props.examples.length === 7
? ""
: `${this.props.examples[0]}
${this.props.examples.slice(1).join(", or\n")}`
// @ts-expect-error TS2550: Property replaceAll does not exist on type string.
.replaceAll("*", "")
.replaceAll("$", "")
.replaceAll("\\ \\text{pi}", " pi")
.replaceAll("\\ ", " and ");
const inputProps = {
id: id,
"aria-describedby": id,
"aria-describedby": ariaId,
ref: "input",
className: this._getInputClassName(),
labelText: this.props.labelText,
Expand All @@ -101,7 +113,14 @@ class InputWithExamples extends React.Component<Props, State> {
autoCorrect: "off",
spellCheck: "false",
};
return <TextInput {...inputProps} />;
return (
<>
<TextInput {...inputProps} />
<span id={ariaId} style={{display: "none"}}>
{examplesAria}
</span>
</>
);
};

_handleFocus: () => void = () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/perseus/src/components/text-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Props = {
placeholder?: string;
onKeyDown?: () => void;
style?: StyleType;
"aria-describedby"?: string;
};

type DefaultProps = {
Expand Down Expand Up @@ -110,6 +111,7 @@ class TextInput extends React.Component<Props> {
value={formattedValue}
type="text"
aria-label={labelText}
aria-describedby={this.props["aria-describedby"]}
onChange={(value) => this.props.onChange(value)}
placeholder={placeholder}
onFocus={onFocus}
Expand Down
1 change: 1 addition & 0 deletions packages/perseus/src/components/tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ class Tooltip extends React.Component<Props, State> {
// eslint-disable-next-line react/no-string-refs
ref="tooltipContainer"
className="tooltipContainer"
role="tooltip"
style={{
position: "absolute",
// height must start out undefined, not null, so that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,7 @@ exports[`multi-item renderer should snapshot: initial render 1`] = `
<span>
<div>
<input
aria-describedby="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
aria-invalid="false"
aria-label="Your answer:"
autocapitalize="off"
Expand All @@ -985,12 +986,17 @@ exports[`multi-item renderer should snapshot: initial render 1`] = `
type="text"
value=""
/>
<span
id="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
style="display: none;"
/>
</div>
<div
style="position: relative; height: 0px; display: none;"
>
<div
class="tooltipContainer"
role="tooltip"
style="position: absolute; left: 0px;"
>
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ exports[`graded-group-set should render all graded groups 1`] = `
<span>
<div>
<input
aria-describedby="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
aria-invalid="false"
aria-label="Your answer:"
autocapitalize="off"
Expand All @@ -72,12 +73,17 @@ exports[`graded-group-set should render all graded groups 1`] = `
type="text"
value=""
/>
<span
id="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
style="display: none;"
/>
</div>
<div
style="position: relative; height: 0px; display: none;"
>
<div
class="tooltipContainer"
role="tooltip"
style="position: absolute; left: 0px;"
>
<div
Expand Down Expand Up @@ -289,6 +295,7 @@ exports[`graded-group-set should render all graded groups 1`] = `
<span>
<div>
<input
aria-describedby="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
aria-invalid="false"
aria-label="Your answer:"
autocapitalize="off"
Expand All @@ -299,12 +306,17 @@ exports[`graded-group-set should render all graded groups 1`] = `
type="text"
value=""
/>
<span
id="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
style="display: none;"
/>
</div>
<div
style="position: relative; height: 0px; display: none;"
>
<div
class="tooltipContainer"
role="tooltip"
style="position: absolute; left: 0px;"
>
<div
Expand Down Expand Up @@ -516,6 +528,7 @@ exports[`graded-group-set should render all graded groups 1`] = `
<span>
<div>
<input
aria-describedby="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
aria-invalid="false"
aria-label="Your answer:"
autocapitalize="off"
Expand All @@ -526,12 +539,17 @@ exports[`graded-group-set should render all graded groups 1`] = `
type="text"
value=""
/>
<span
id="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
style="display: none;"
/>
</div>
<div
style="position: relative; height: 0px; display: none;"
>
<div
class="tooltipContainer"
role="tooltip"
style="position: absolute; left: 0px;"
>
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ exports[`graded group widget should snapshot 1`] = `
<span>
<div>
<input
aria-describedby="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
aria-invalid="false"
aria-label="Your answer:"
autocapitalize="off"
Expand All @@ -138,12 +139,17 @@ exports[`graded group widget should snapshot 1`] = `
type="text"
value=""
/>
<span
id="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
style="display: none;"
/>
</div>
<div
style="position: relative; height: 0px; display: none;"
>
<div
class="tooltipContainer"
role="tooltip"
style="position: absolute; left: 0px;"
>
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,7 @@ exports[`group widget should snapshot: initial render 1`] = `
<span>
<div>
<input
aria-describedby="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
aria-invalid="false"
aria-label="value rounded to the nearest ten"
autocapitalize="off"
Expand All @@ -765,12 +766,17 @@ exports[`group widget should snapshot: initial render 1`] = `
type="text"
value=""
/>
<span
id="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
style="display: none;"
/>
</div>
<div
style="position: relative; height: 0px; display: none;"
>
<div
class="tooltipContainer"
role="tooltip"
style="position: absolute; left: 0px;"
>
<div
Expand Down Expand Up @@ -960,6 +966,7 @@ exports[`group widget should snapshot: initial render 1`] = `
<span>
<div>
<input
aria-describedby="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAy"
aria-invalid="false"
aria-label="value rounded to the nearest hundred"
autocapitalize="off"
Expand All @@ -970,12 +977,17 @@ exports[`group widget should snapshot: initial render 1`] = `
type="text"
value=""
/>
<span
id="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAy"
style="display: none;"
/>
</div>
<div
style="position: relative; height: 0px; display: none;"
>
<div
class="tooltipContainer"
role="tooltip"
style="position: absolute; left: 0px;"
>
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ exports[`numeric-input widget Should render predictably: after interaction 1`] =
<span>
<div>
<input
aria-describedby="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
aria-invalid="false"
aria-label="Your answer:"
autocapitalize="off"
Expand All @@ -103,12 +104,17 @@ exports[`numeric-input widget Should render predictably: after interaction 1`] =
type="text"
value="1252"
/>
<span
id="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
style="display: none;"
/>
</div>
<div
style="position: relative; height: 0px; display: none;"
>
<div
class="tooltipContainer"
role="tooltip"
style="position: absolute; left: 0px;"
>
<div
Expand Down Expand Up @@ -295,6 +301,7 @@ exports[`numeric-input widget Should render predictably: first render 1`] = `
<span>
<div>
<input
aria-describedby="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
aria-invalid="false"
aria-label="Your answer:"
autocapitalize="off"
Expand All @@ -305,12 +312,17 @@ exports[`numeric-input widget Should render predictably: first render 1`] = `
type="text"
value=""
/>
<span
id="aria-for-input-with-examples-bnVtZXJpYy1pbnB1dCAx"
style="display: none;"
/>
</div>
<div
style="position: relative; height: 0px; display: none;"
>
<div
class="tooltipContainer"
role="tooltip"
style="position: absolute; left: 0px;"
>
<div
Expand Down
45 changes: 45 additions & 0 deletions packages/perseus/src/widgets/numeric-input/numeric-input.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,51 @@ describe("numeric-input widget", () => {
// Assert
expect(renderer).toHaveBeenAnsweredIncorrectly();
});

it("Should render an element with format options as text for use by assistive technologies", async () => {
// Arrange - Fractions
const questionWithFormatOptions = JSON.parse(JSON.stringify(question1));
questionWithFormatOptions.widgets[
"numeric-input 1"
].options.answers[0].answerForms = ["proper", "improper", "mixed"];

// Act
const fractionsContainer = renderQuestion(
questionWithFormatOptions,
).container;
// eslint-disable-next-line testing-library/no-node-access
const fractionTextContainer = fractionsContainer.querySelector(
"[id*='aria-for-input-with-examples-']",
);

// Assert
expect(fractionTextContainer).toHaveTextContent(
"a simplified proper fraction",
);
expect(fractionTextContainer).toHaveTextContent(
"a simplified improper fraction",
);
expect(fractionTextContainer).toHaveTextContent("a mixed number");

// Arrange - Non-Fractions
questionWithFormatOptions.widgets[
"numeric-input 1"
].options.answers[0].answerForms = ["integer", "decimal", "pi"];

// Act
const othersContainer = renderQuestion(
questionWithFormatOptions,
).container;
// eslint-disable-next-line testing-library/no-node-access
const othersTextContainer = othersContainer.querySelector(
"[id*='aria-for-input-with-examples-']",
);

// Assert
expect(othersTextContainer).toHaveTextContent("an integer");
expect(othersTextContainer).toHaveTextContent("an exact decimal");
expect(othersTextContainer).toHaveTextContent("a multiple of pi");
});
});

describe("static function getOneCorrectAnswerFromRubric", () => {
Expand Down