Skip to content

Commit

Permalink
Make the Expression widget treat sen as equivalent to sin (#921)
Browse files Browse the repository at this point in the history
## Summary:
`sen` is used instead of `sin` in Portuguese. This change allows the
Expression widget to accept both spellings. We simply convert any
instances of `sen` to `sin` when evaluating the learner's answer.

Issue: https://khanacademy.atlassian.net/browse/LC-290

Test plan:

- Start storybook: `yarn storybook`.
- Visit
  `http://localhost:6006/?path=/docs/perseus-widgets-expression--docs`.
  Enter `sen(x)` in one of the input fields. The word `sen` should be
  displayed in an upright font, indicating that it is an operator. `sin`,
  `cos`, `tan` etc. should also be displayed in an upright font.

<img width="150" alt="Screen Shot 2024-01-11 at 2 26 02 PM" src="https://github.com/Khan/perseus/assets/693920/5788c5e4-b42b-49b8-b680-773284a7dfb7">

Author: benchristel

Reviewers: nedredmond

Required Reviewers:

Approved By: nedredmond

Checks: ✅ codecov/project, ✅ codecov/patch, ✅ Upload Coverage, ✅ Publish npm snapshot (ubuntu-latest, 20.x), ✅ Publish Storybook to Chromatic (ubuntu-latest, 20.x), ✅ Extract i18n strings (ubuntu-latest, 20.x), ✅ Jest Coverage (ubuntu-latest, 20.x), ✅ Check builds for changes in size (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), ✅ gerald

Pull Request URL: #921
  • Loading branch information
benchristel authored Jan 11, 2024
1 parent 50c617d commit 81b9a56
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 2 deletions.
7 changes: 7 additions & 0 deletions .changeset/new-colts-sip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@khanacademy/math-input": minor
"@khanacademy/perseus": minor
---

Make the Expression widget treat `sen` as equivalent to `sin`. The spelling
`sen` is used in Portuguese.
40 changes: 40 additions & 0 deletions packages/math-input/src/components/input/mathquill-instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,46 @@ function createBaseConfig(): MathFieldConfig {
// appropriate symbol. This does not include ln, log, or any of the
// trig functions; those are always interpreted as commands.
autoCommands: "pi theta phi sqrt nthroot",
// Most of these autoOperatorNames are simply the MathQuill defaults.
// We have to list them all in order to add the `sen` operator (see
// comment below).
autoOperatorNames: [
"arccos",
"arcsin",
"arctan",
"arg",
"cos",
"cosh",
"cot",
"coth",
"csc",
"deg",
"det",
"dim",
"exp",
"gcd",
"hom",
"inf",
"ker",
"lg",
"lim",
"liminf",
"limsup",
"ln",
"log",
"max",
"min",
"Pr",
"projlim",
"sec",
// sen is used instead of sin in e.g. Portuguese
"sen",
"sin",
"sinh",
"sup",
"tan",
"tanh",
].join(" "),

// Pop the cursor out of super/subscripts on arithmetic operators
// or (in)equalities.
Expand Down
20 changes: 20 additions & 0 deletions packages/perseus/src/widgets/__testdata__/expression.testdata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,26 @@ const createItemJson = (
};
};

export const expressionItemWithAnswer = (answer: string): PerseusItem => {
return createItemJson(
{
answerForms: [
{
considered: "correct",
form: false,
simplify: false,
value: answer,
},
],
times: false,
buttonSets: ["basic"],
functions: [],
buttonsVisible: "always",
},
{major: 1, minor: 0},
);
};

export const expressionItem2: PerseusItem = createItemJson(
{
answerForms: [
Expand Down
45 changes: 45 additions & 0 deletions packages/perseus/src/widgets/__tests__/expression.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
expressionItem2,
expressionItem3,
expressionItem3Options,
expressionItemWithAnswer,
} from "../__testdata__/expression.testdata";
import {Expression} from "../expression";

Expand Down Expand Up @@ -182,6 +183,33 @@ describe("Expression Widget", function () {
});
});

describe("when the question uses the sin function", () => {
it("allows parens", () => {
const item = expressionItemWithAnswer("sin(x)");
assertCorrect(item, "sin(x)");
});

it("allows no parens", () => {
const item = expressionItemWithAnswer("sin(x)");
assertCorrect(item, "sin x");
});

it("grades a wrong answer as incorrect", () => {
const item = expressionItemWithAnswer("sin(x)");
assertIncorrect(item, "2");
});

it("treats sen as equivalent to sin", () => {
const item = expressionItemWithAnswer("sin(x)");
assertCorrect(item, "sen x");
});

it("treats multiple usages of sen as equivalent to sin", () => {
const item = expressionItemWithAnswer("sin(sin(x))");
assertCorrect(item, "sen(sen(x))");
});
});

describe("analytics", () => {
const assertKeypadVersion = (
apiOptions: APIOptions,
Expand Down Expand Up @@ -436,4 +464,21 @@ describe("error tooltip", () => {
screen.getByText("Sorry, I don't understand that!"),
).toBeVisible();
});

it("does not show error text when the sen() function is used (Portuguese for sin())", async () => {
// Arrange
const {renderer} = renderQuestion(expressionItem2.question);
const expression = renderer.findWidgets("expression 1")[0];

// Act
expression.insert("sen(x)");
screen.getByRole("textbox").blur();
renderer.guessAndScore();

// Assert
expect(screen.queryByText("Oops!")).toBeNull();
expect(
screen.queryByText("Sorry, I don't understand that!"),
).toBeNull();
});
});
15 changes: 13 additions & 2 deletions packages/perseus/src/widgets/expression.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ const insertBraces = (value) => {
return value.replace(/([_^])([^{])/g, "$1{$2}");
};

const anglicizeOperators = (tex: string): string => {
// sen is used instead of sin in some languages, e.g. Portuguese.
// To ensure that answers in various languages are graded correctly, we
// convert operators to their Englishy forms.
return tex.replace(/\\operatorname{sen}/g, "\\sin ");
};

const normalizeTex = (tex: string): string => {
return anglicizeOperators(insertBraces(tex));
};

const deriveKeypadVersion = (apiOptions: APIOptions) => {
// We can derive which version of the keypad is in use. This is
// a bit tricky, but this code will be relatively short-lived
Expand Down Expand Up @@ -280,7 +291,7 @@ export class Expression extends React.Component<Props, ExpressionState> {
}

static getUserInputFromProps(props: Props): string {
return insertBraces(props.value);
return normalizeTex(props.value);
}

static getOneCorrectAnswerFromRubric(
Expand Down Expand Up @@ -410,7 +421,7 @@ export class Expression extends React.Component<Props, ExpressionState> {
_.extend(options, {
decimal_separator: i18n.getDecimalSeparator(),
});
return KAS.parse(insertBraces(value), options);
return KAS.parse(normalizeTex(value), options);
};

changeAndTrack: (e: any, cb: () => void) => void = (
Expand Down

0 comments on commit 81b9a56

Please sign in to comment.