Skip to content

Commit b18e88e

Browse files
This is a feature-branch pull-request from feature/numeric-dx-refactor to main (#2209)
## Summary: This is the Feature Branch for the Numeric Input Project. This PR includes the following commits: - Move Numeric Input's UI related code to a functional component (#2108) - Refactoring Numeric Input helper functions to remove underscore (#2128) - Modernization and Migration of InputWithExamples to NumericInput folder (#2121) - [its-a-numeric-story] Improving Numeric Input Storybook Stories (#2138) - Ensure that Numeric Input Examples only show for correct answers. (#2159) - [3-quell-the-fracus] [Bug Fix] - Ensure that users hand typing tex into Numeric Inputs on Desktop do not cause an infinite loop. (#2182) - [fool-tips] Tooltip is Dead. Long Live Tooltip. (#2195) - [whole-fraction-fix] Ensure that whole numbers are not accepted answers for Numeric Inputs that require Improper fractions. (#2203) - [scrollin-aint-right] Fix right align scrollbars in Numeric Input (#2187) - [message-nit] Minor string adjustment (#2204) ## Test plan: - tests pass - lint passes - Passed a Full QA Regression Suite Author: SonicScrewdriver Reviewers: nishasy, SonicScrewdriver, handeyeco, mark-fitzgerald Required Reviewers: Approved By: nishasy Checks: ✅ Publish npm snapshot (ubuntu-latest, 20.x), ✅ Lint, Typecheck, Format, and Test (ubuntu-latest, 20.x), ✅ Cypress (ubuntu-latest, 20.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ✅ Check builds for changes in size (ubuntu-latest, 20.x), ✅ Publish Storybook to Chromatic (ubuntu-latest, 20.x), ✅ Prime node_modules cache for primary configuration (ubuntu-latest, 20.x) Pull Request URL: #2209
2 parents e53c2ac + 86bb214 commit b18e88e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+2174
-2928
lines changed

.changeset/brave-mice-cheer.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/perseus": patch
3+
---
4+
5+
Fixing changes that did not migrate automatically while rebasing feature branch.

.changeset/chilled-mugs-greet.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/perseus-score": minor
3+
---
4+
5+
Bugfix to ensure that Numerics that require Improper fractions don't accept whole numbers.

.changeset/clean-ads-push.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/perseus": patch
3+
---
4+
5+
Update aria label for whe ncontent creators do not provide one

.changeset/dirty-seahorses-sneeze.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/perseus": patch
3+
---
4+
5+
Ensure Numeric Input Tooltips display under the input.

.changeset/five-geckos-jam.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/math-input": patch
3+
---
4+
5+
Fixing bug where scrollbars would appear in Math Input when right aligned.

.changeset/five-rivers-fail.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/perseus-editor": patch
3+
---
4+
5+
Minor string update for Numeric editor

.changeset/pink-buttons-clap.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/perseus-score": patch
3+
---
4+
5+
Bugfix to ensure users cannot create infinite loop with incomplete tex in Numeric Input

.changeset/purple-gifts-brake.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/perseus": minor
3+
---
4+
5+
Update Input with Examples to use Wonderblocks Tooltip

.changeset/rich-flowers-prove.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@khanacademy/math-input": minor
3+
"@khanacademy/perseus": minor
4+
---
5+
6+
Modernization and Migration of InputWithExamples to NumericInput folder

.changeset/rotten-kangaroos-fly.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/perseus": patch
3+
---
4+
5+
Bug fix to ensure that Numeric Examples only show for correct answers.

.changeset/sharp-peaches-love.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@khanacademy/math-input": minor
3+
"@khanacademy/perseus": minor
4+
"@khanacademy/perseus-core": minor
5+
---
6+
7+
Refactoring Numeric Input helper functions to remove underscore, improve documentation, and add tests.

.changeset/six-cars-agree.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/perseus": patch
3+
---
4+
5+
Cleanup of Numeric Input stories

.changeset/smart-countries-hunt.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@khanacademy/math-input": minor
3+
"@khanacademy/perseus": minor
4+
---
5+
6+
Refactoring Numeric Input to move UI-logic to functional component.

.changeset/weak-zoos-march.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/perseus": patch
3+
---
4+
5+
Update tests and fix snapshots from feature branch rebase

packages/math-input/src/components/input/math-input.tsx

+8-2
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,9 @@ class MathInput extends React.Component<Props, State> {
353353
});
354354
};
355355

356+
// [Jan 2025] Third: While testing, I've discovered that we likely don't
357+
// need to be passing setKeypadActive here at all. Removing the parameter
358+
// still results in the same behavior.
356359
focus: (setKeypadActive: KeypadContextType["setKeypadActive"]) => void = (
357360
setKeypadActive,
358361
) => {
@@ -931,11 +934,14 @@ class MathInput extends React.Component<Props, State> {
931934
// padding values for the vertical directions.
932935
const symbolaPaddingBottom = 3;
933936
const symbolaPaddingTop = 1;
937+
// We also add a little padding for the cursor to ensure there's no
938+
// overflow when the input is empty and set to right aligned.
939+
const cursorPadding = 2;
934940
const padding = {
935941
paddingTop: paddingInset - symbolaPaddingTop,
936-
paddingRight: paddingInset,
942+
paddingRight: paddingInset + cursorPadding,
937943
paddingBottom: paddingInset - symbolaPaddingBottom,
938-
paddingLeft: paddingInset,
944+
paddingLeft: paddingInset + cursorPadding,
939945
} as const;
940946

941947
return padding;

packages/perseus-core/src/data-schema.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -1181,16 +1181,16 @@ export type MathFormat =
11811181
| "pi";
11821182

11831183
export type PerseusNumericInputAnswerForm = {
1184-
simplify:
1185-
| "required"
1186-
| "correct"
1187-
| "enforced"
1188-
| "optional"
1189-
| null
1190-
| undefined;
1184+
simplify: PerseusNumericInputSimplify | null | undefined;
11911185
name: MathFormat;
11921186
};
11931187

1188+
export type PerseusNumericInputSimplify =
1189+
| "required"
1190+
| "correct"
1191+
| "enforced"
1192+
| "optional";
1193+
11941194
export type PerseusNumericInputWidgetOptions = {
11951195
// A list of all the possible correct and incorrect answers
11961196
answers: ReadonlyArray<PerseusNumericInputAnswer>;
@@ -1227,7 +1227,7 @@ export type PerseusNumericInputAnswer = {
12271227
// NOTE: perseus_data.go says this is non-nullable even though we handle null values.
12281228
maxError: number | null | undefined;
12291229
// Unsimplified answers are Ungraded, Accepted, or Wrong. Options: "required", "correct", or "enforced"
1230-
simplify: string | null | undefined;
1230+
simplify: PerseusNumericInputSimplify | null | undefined;
12311231
};
12321232

12331233
export type PerseusNumberLineWidgetOptions = {

packages/perseus-core/src/parse-perseus-json/perseus-parsers/numeric-input-widget.ts

+16-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ const parseMathFormat = enumeration(
2929
"pi",
3030
);
3131

32+
const parseSimplify = enumeration(
33+
"required",
34+
"correct",
35+
"enforced",
36+
"optional",
37+
);
38+
3239
export const parseNumericInputWidget: Parser<NumericInputWidget> = parseWidget(
3340
constant("numeric-input"),
3441
object({
@@ -48,8 +55,15 @@ export const parseNumericInputWidget: Parser<NumericInputWidget> = parseWidget(
4855
// the data, we should simplify `simplify`.
4956
simplify: optional(
5057
nullable(
51-
union(string).or(
52-
pipeParsers(boolean).then(convert(String)).parser,
58+
union(parseSimplify).or(
59+
pipeParsers(boolean).then(
60+
convert((value) => {
61+
if (typeof value === "boolean") {
62+
return value ? "required" : "optional";
63+
}
64+
return value;
65+
}),
66+
).parser,
5367
).parser,
5468
),
5569
),

packages/perseus-core/src/parse-perseus-json/regression-tests/__snapshots__/parse-perseus-json-regression.test.ts.snap

+2-2
Original file line numberDiff line numberDiff line change
@@ -6791,7 +6791,7 @@ exports[`parseAndMigratePerseusItem given numeric-input-answer-with-simplify-tru
67916791
{
67926792
"maxError": 0,
67936793
"message": "",
6794-
"simplify": "true",
6794+
"simplify": "required",
67956795
"status": "correct",
67966796
"strict": false,
67976797
"value": 1.125,
@@ -7704,7 +7704,7 @@ exports[`parseAndMigratePerseusItem given numeric-input-with-simplify-false.json
77047704
{
77057705
"maxError": 0,
77067706
"message": "",
7707-
"simplify": "false",
7707+
"simplify": "optional",
77087708
"status": "correct",
77097709
"strict": false,
77107710
"value": 2.6,

packages/perseus-editor/src/__stories__/editor.stories.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {action} from "@storybook/addon-actions";
55
import * as React from "react";
66

77
import {Editor} from "..";
8-
import SideBySide from "../../../../testing/side-by-side";
8+
import SplitView from "../../../../testing/split-view";
99
import {question1} from "../__testdata__/numeric-input.testdata";
1010
import {registerAllWidgetsAndEditorsForTesting} from "../util/register-all-widgets-and-editors-for-testing";
1111

@@ -84,9 +84,9 @@ export const DemoInteractiveGraph = (): React.ReactElement => {
8484
// class to be above it.
8585
// TODO: Refactor to aphrodite styles instead of scoped CSS in Less.
8686
<div className="framework-perseus">
87-
<SideBySide
88-
leftTitle="Editor"
89-
left={
87+
<SplitView
88+
rendererTitle="Editor"
89+
renderer={
9090
<View style={{width: "360px", margin: "20px"}}>
9191
<Editor
9292
ref={editorRef}
@@ -127,7 +127,7 @@ export const DemoInteractiveGraph = (): React.ReactElement => {
127127
/>
128128
</View>
129129
}
130-
rightTitle="Serialized Widget Options"
130+
JSONTitle="Serialized Widget Options"
131131
jsonObject={options}
132132
/>
133133
</div>

packages/perseus-editor/src/widgets/numeric-input-editor.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -687,7 +687,7 @@ class NumericInputEditor extends React.Component<Props, State> {
687687
</fieldset>
688688
{unsimplifiedAnswers(i)}
689689
<div className="perseus-widget-row">
690-
Message shown to user in article:
690+
(Articles only) Message shown to user:
691691
</div>
692692
{editor}
693693
{suggestedAnswerTypes(i)}

packages/perseus-score/src/util/answer-types.ts

+11
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,17 @@ const KhanAnswerTypes = {
241241

242242
// an improper fraction
243243
improper: function (text) {
244+
// As our answer keys are always in simplest form, we need
245+
// to check for the existence of a fraction in the input before
246+
// validating the answer. If no fraction is found, we consider
247+
// the answer to be incorrect.
248+
const fractionExists: boolean =
249+
text.includes("/") || text.match(/\\(d?frac)/);
250+
251+
if (!fractionExists) {
252+
return [];
253+
}
254+
244255
return $.map(fractionTransformer(text), function (o) {
245256
// All fractions that are greater than 1
246257
if (Math.abs(o.value) >= 1) {

packages/perseus-score/src/util/tex-wrangler.test.ts

+19
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,22 @@ describe("parseTex", () => {
7878
assertParsed(undefined, "");
7979
});
8080
});
81+
describe("parseTex", () => {
82+
it("should replace single fractions", () => {
83+
assertParsed("\\dfrac{3}{4}", "3 / 4");
84+
});
85+
86+
it("should remove blackslash-escapes for percent signs", () => {
87+
assertParsed("3\\%", "3%");
88+
assertParsed("3.5\\%", "3.5%");
89+
assertParsed("\\dfrac{3\\%}{4}", "3% / 4");
90+
});
91+
92+
it("should not throw error when input is undefined", () => {
93+
assertParsed(undefined, "");
94+
});
95+
96+
it("should not cause an infinite loop if provided incomplete tex commands", () => {
97+
assertParsed("\\frac", "/");
98+
});
99+
});

packages/perseus-score/src/util/tex-wrangler.ts

+12
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,23 @@ function parseNextExpression(
4242
// Find the first '{' and grab subsequent TeX
4343
// Ex) tex: '{3}{7}', and we want the '3'
4444
const openBracketIndex = tex.indexOf("{", currentIndex);
45+
46+
// If there is no open bracket, set the endpoint to the end of the string
47+
// and the expression to an empty string. This helps ensure we don't
48+
// get stuck in an infinite loop when users handtype TeX.
49+
if (openBracketIndex === -1) {
50+
return {
51+
endpoint: tex.length,
52+
expression: "",
53+
};
54+
}
55+
4556
const nextExpIndex = openBracketIndex + 1;
4657

4758
// Truncate to only contain remaining TeX
4859
const endpoint = findEndpoint(tex, nextExpIndex);
4960
const expressionTeX = tex.substring(nextExpIndex, endpoint);
61+
5062
const parsedExp = walkTex(expressionTeX, handler);
5163

5264
return {

0 commit comments

Comments
 (0)