Skip to content

Commit

Permalink
[Locked Figures] Use $ symbols to denote TeX within locked labels and…
Browse files Browse the repository at this point in the history
… locked figure labels (#1834)

## Summary:
In the rest of the exercise editor, content authors need to surround text in `$` for it to be denoted as TeX.

For consistency, we want to add that functionality to the interactive graph editor as well.

- Update LockedLabelSettings to say "text" instead of "TeX" and have an infotip to explain it.
- Update LockedLabel so that it surrounds the non-$ part of the string with \text{} before
  wrapping the whole thing in <TeX>


Note: This is a prerequisite for auto-generating spoken math details with the MathJax speech engine
for the aria labels, because the non-TeX text needs to be differentiated in the label for the label
to be generated correctly for non-TeX text. If it's all TeX, the speech engine reads every letter
separately (example: "<TeX>square A</TeX>" ==> "s q u a r e upper A")

Issue: https://khanacademy.atlassian.net/browse/LEMS-2548

## Test plan:
`yarn jest packages/perseus/src/widgets/interactive-graphs/utils.test.ts`

Storybook
- http://localhost:6006/?path=/story/perseuseditor-widgets-interactive-graph--mafs-with-locked-figure-labels-all-flags
- Play around with the locked label and the locked figure labels

Author: nishasy

Reviewers: nishasy, anakaren-rojas, #perseus, benchristel, catandthemachines

Required Reviewers:

Approved By: anakaren-rojas

Checks: ✅ Publish npm snapshot (ubuntu-latest, 20.x), ✅ Cypress (ubuntu-latest, 20.x), ✅ Check builds for changes in size (ubuntu-latest, 20.x), ✅ Lint, Typecheck, Format, and Test (ubuntu-latest, 20.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ✅ Publish Storybook to Chromatic (ubuntu-latest, 20.x), ✅ gerald

Pull Request URL: #1834
  • Loading branch information
nishasy authored Nov 7, 2024
1 parent 122b3cc commit 429b9cc
Show file tree
Hide file tree
Showing 13 changed files with 125 additions and 25 deletions.
6 changes: 6 additions & 0 deletions .changeset/good-ghosts-invite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@khanacademy/perseus": minor
"@khanacademy/perseus-editor": minor
---

[Locked Figures] Use \$ symbols to denote TeX within locked labels and locked figure labels
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ describe("LockedEllipseSettings", () => {
);

// Act
const labelText = screen.getByLabelText("TeX");
const labelText = screen.getByLabelText("text");
await userEvent.type(labelText, "!");

// Assert
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ describe("Locked Function Settings", () => {
);

// Act
const labelText = screen.getByLabelText("TeX");
const labelText = screen.getByLabelText("text");
await userEvent.type(labelText, "!");

// Assert
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ describe("Locked Label Settings", () => {

// Act
const textInput = screen.getByRole("textbox", {
name: "TeX",
name: "text",
});
await userEvent.type(textInput, "x^2");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
type LockedFigure,
type LockedFigureColor,
type LockedLabelType,
components,
} from "@khanacademy/perseus";
import {View} from "@khanacademy/wonder-blocks-core";
import {OptionItem, SingleSelect} from "@khanacademy/wonder-blocks-dropdown";
Expand All @@ -29,6 +30,8 @@ import LockedFigureSettingsActions from "./locked-figure-settings-actions";
import type {LockedFigureSettingsMovementType} from "./locked-figure-settings-actions";
import type {StyleType} from "@khanacademy/wonder-blocks-core";

const {InfoTip} = components;

export type Props = LockedLabelType & {
/**
* Called when the props (coord, color, etc.) are updated.
Expand Down Expand Up @@ -115,19 +118,34 @@ export default function LockedLabelSettings(props: Props) {
/>

{/* Text settings */}
<LabelMedium tag="label" style={[styles.row, styles.spaceUnder]}>
TeX
<Strut size={spacing.xSmall_8} />
<TextField
value={text}
placeholder="ex. x^2 or \frac{1}{2}"
onChange={(newValue) =>
onChangeProps({
text: newValue,
})
}
/>
</LabelMedium>
<View style={styles.row}>
<LabelMedium
tag="label"
style={[styles.row, styles.spaceUnder, {flexGrow: 1}]}
>
text
<Strut size={spacing.xSmall_8} />
<TextField
value={text}
placeholder="ex. x^2 or \frac{1}{2}"
onChange={(newValue) =>
onChangeProps({
text: newValue,
})
}
/>
</LabelMedium>
<InfoTip>
Surround your text with $ for TeX.
<br />
Example: {`This circle has radius $\\frac{1}{2}$ units.`}
<br />
<br />
It is important to use TeX when appropriate for
accessibility. The above example would be read as "This
circle has radius one-half units" by screen readers.
</InfoTip>
</View>

<View style={styles.row}>
<ColorSelect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ describe("LockedLineSettings", () => {
);

// Act
const labelText = screen.getByLabelText("TeX");
const labelText = screen.getByLabelText("text");
await userEvent.type(labelText, "!");

// Assert
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ describe("LockedPointSettings", () => {
);

// Act
const labelText = screen.getByLabelText("TeX");
const labelText = screen.getByLabelText("text");
await userEvent.type(labelText, "!");

// Assert
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ describe("LockedPolygonSettings", () => {
);

// Assert
const inputField = screen.getByRole("textbox", {name: "TeX"});
const inputField = screen.getByRole("textbox", {name: "text"});
expect(inputField).toHaveValue("label text");
});

Expand Down Expand Up @@ -447,7 +447,7 @@ describe("LockedPolygonSettings", () => {
);

// Act
const labelText = screen.getByLabelText("TeX");
const labelText = screen.getByLabelText("text");
await userEvent.type(labelText, "!");

// Assert
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ describe("Locked Vector Settings", () => {
);

// Act
const labelText = screen.getByLabelText("TeX");
const labelText = screen.getByLabelText("text");
await userEvent.type(labelText, "!");

// Assert
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -877,7 +877,7 @@ export const segmentWithLockedFigures: PerseusRenderer =
labels: [{text: "F"}],
ariaLabel: "Function F",
})
.addLockedLabel("\\sqrt{\\frac{1}{2}}", [6, -5])
.addLockedLabel("$\\sqrt{\\frac{1}{2}}$", [6, -5])
.build();

export const quadraticQuestion: PerseusRenderer =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {getDependencies} from "../../../dependencies";
import {lockedFigureColors, type LockedLabelType} from "../../../perseus-types";
import {pointToPixel} from "../graphs/use-transform";
import useGraphConfig from "../reducer/use-graph-config";
import {replaceOutsideTeX} from "../utils";

export default function LockedLabel(props: LockedLabelType) {
const {coord, text, color, size} = props;
Expand All @@ -13,7 +14,7 @@ export default function LockedLabel(props: LockedLabelType) {

const {TeX} = getDependencies();

// Move this all outside the SVG element
// Note: The TeX component cannot be rendered within an SVG
return (
<span
className="locked-label"
Expand All @@ -27,7 +28,7 @@ export default function LockedLabel(props: LockedLabelType) {
}}
aria-hidden={true}
>
<TeX>{text}</TeX>
<TeX>{replaceOutsideTeX(text)}</TeX>
</span>
);
}
57 changes: 56 additions & 1 deletion packages/perseus/src/widgets/interactive-graphs/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {normalizePoints, normalizeCoords} from "./utils";
import {normalizePoints, normalizeCoords, replaceOutsideTeX} from "./utils";

import type {Coord} from "../../interactive2/types";
import type {GraphRange} from "../../perseus-types";
Expand Down Expand Up @@ -65,3 +65,58 @@ describe("normalizeCoords", () => {
expect(result).toEqual(expected);
});
});

describe("replaceOutsideTeX", () => {
test("no $s", () => {
const mathString = "x^2";
const convertedString = replaceOutsideTeX(mathString);

expect(convertedString).toEqual("\\text{x^2}");
});

test("$s surrounding string", () => {
const mathString = "$x^2$";
const convertedString = replaceOutsideTeX(mathString);

expect(convertedString).toEqual("x^2");
});

test("$s within string", () => {
const mathString = "Expression $x^2$ is exponential";
const convertedString = replaceOutsideTeX(mathString);

expect(convertedString).toEqual(
"\\text{Expression }x^2\\text{ is exponential}",
);
});

test("$s first", () => {
const mathString = "$A$ is square";
const convertedString = replaceOutsideTeX(mathString);

expect(convertedString).toEqual("A\\text{ is square}");
});

test("regular text first", () => {
const mathString = "Square $A$";
const convertedString = replaceOutsideTeX(mathString);

expect(convertedString).toEqual("\\text{Square }A");
});

test("multiple $s", () => {
const mathString = "$A$ is $B$";
const convertedString = replaceOutsideTeX(mathString);

expect(convertedString).toEqual("A\\text{ is }B");
});

test("multiple $s with surrounding text", () => {
const mathString = "Square $A$ is $B$ also";
const convertedString = replaceOutsideTeX(mathString);

expect(convertedString).toEqual(
"\\text{Square }A\\text{ is }B\\text{ also}",
);
});
});
20 changes: 20 additions & 0 deletions packages/perseus/src/widgets/interactive-graphs/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,23 @@ export function isUnlimitedGraphState(
(state.type === "polygon" && state.numSides === "unlimited")
);
}

/**
* Replace all text outside of the $ TeX blocks with `\\text{...}`
* This way, the entire resulting string can be rendered within <TeX>
* and the text outside of the $ blocks will be non-TeX text.
*/
export function replaceOutsideTeX(mathString: string) {
let currentlyTeX = mathString[0] === "$";
let result = "";

const splitString = mathString.split("$").filter((part) => part !== "");

for (let i = 0; i < splitString.length; i++) {
const part = splitString[i];
result += currentlyTeX ? part : `\\text{${part}}`;
currentlyTeX = !currentlyTeX;
}

return result;
}

0 comments on commit 429b9cc

Please sign in to comment.