diff --git a/packages/math-input/src/components/key-handlers/key-translator.ts b/packages/math-input/src/components/key-handlers/key-translator.ts index ced26e7254..6d5b8347bf 100644 --- a/packages/math-input/src/components/key-handlers/key-translator.ts +++ b/packages/math-input/src/components/key-handlers/key-translator.ts @@ -1,5 +1,6 @@ +import {getDecimalSeparator} from "@khanacademy/perseus-core"; + import {MathFieldActionType} from "../../types"; -import {getDecimalSeparator} from "../../utils"; import {mathQuillInstance} from "../input/mathquill-instance"; import handleArrow from "./handle-arrow"; diff --git a/packages/math-input/src/components/keypad/button-assets.tsx b/packages/math-input/src/components/keypad/button-assets.tsx index 732efcd596..5803f6ceca 100644 --- a/packages/math-input/src/components/keypad/button-assets.tsx +++ b/packages/math-input/src/components/keypad/button-assets.tsx @@ -10,9 +10,9 @@ asset. In the future it would be great if these were included from files so that no copying and pasting is necessary. */ +import {getDecimalSeparator} from "@khanacademy/perseus-core"; import * as React from "react"; -import {DecimalSeparator, getDecimalSeparator} from "../../utils"; import {useMathInputI18n} from "../i18n-context"; import type Key from "../../data/keys"; @@ -176,10 +176,7 @@ export default function ButtonAsset({id}: Props): React.ReactNode { case "PERIOD": // Different locales use different symbols for the decimal separator // (, vs .) - if ( - id === "DECIMAL" && - getDecimalSeparator(locale) === DecimalSeparator.COMMA - ) { + if (id === "DECIMAL" && getDecimalSeparator(locale) !== ".") { // comma decimal separator return ( { - let separator: string = DecimalSeparator.PERIOD; - - switch (locale) { - // TODO(somewhatabstract): Remove this when Chrome supports the `ka` - // locale properly. - // https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201 - // - // Supported locales in Chrome: - // https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list - case "ka": - separator = ","; - break; - - default: - const numberWithDecimalSeparator = 1.1; - // TODO(FEI-3647): Update to use .formatToParts() once we no longer have to - // support Safari 12. - const match = new Intl.NumberFormat(locale) - .format(numberWithDecimalSeparator) - // 0x661 is ARABIC-INDIC DIGIT ONE - // 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE - .match(/[^\d\u0661\u06F1]/); - separator = match?.[0] ?? "."; - } - - return separator === "," ? DecimalSeparator.COMMA : DecimalSeparator.PERIOD; -}; - const CDOT_ONLY = [ "az", "cs", diff --git a/packages/perseus-core/src/index.ts b/packages/perseus-core/src/index.ts index d13bf5e948..93b0447bcb 100644 --- a/packages/perseus-core/src/index.ts +++ b/packages/perseus-core/src/index.ts @@ -12,6 +12,7 @@ export type {ErrorKind} from "./error/errors"; // Careful, `version.ts` uses this function so it _must_ be imported above it export {addLibraryVersionToPerseusDebug} from "./utils/add-library-version-to-perseus-debug"; export {default as getMatrixSize} from "./utils/get-matrix-size"; +export {default as getDecimalSeparator} from "./utils/get-decimal-separator"; export {libVersion} from "./version"; diff --git a/packages/perseus/src/widgets/expression/get-decimal-separator.ts b/packages/perseus-core/src/utils/get-decimal-separator.ts similarity index 100% rename from packages/perseus/src/widgets/expression/get-decimal-separator.ts rename to packages/perseus-core/src/utils/get-decimal-separator.ts diff --git a/packages/perseus-score/src/index.ts b/packages/perseus-score/src/index.ts index db68fcbebf..daeb9a9a56 100644 --- a/packages/perseus-score/src/index.ts +++ b/packages/perseus-score/src/index.ts @@ -5,6 +5,7 @@ export type * from "./validation.types"; export {default as scoreCategorizer} from "./widgets/categorizer/score-categorizer"; export {default as scoreCSProgram} from "./widgets/cs-program/score-cs-program"; export {default as scoreDropdown} from "./widgets/dropdown/score-dropdown"; +export {default as scoreExpression} from "./widgets/expression/score-expression"; export {default as scoreIframe} from "./widgets/iframe/score-iframe"; export { default as scoreLabelImage, diff --git a/packages/perseus/src/widgets/expression/score-expression.test.ts b/packages/perseus-score/src/widgets/expression/score-expression.test.ts similarity index 96% rename from packages/perseus/src/widgets/expression/score-expression.test.ts rename to packages/perseus-score/src/widgets/expression/score-expression.test.ts index 97b78f9019..2bab201af6 100644 --- a/packages/perseus/src/widgets/expression/score-expression.test.ts +++ b/packages/perseus-score/src/widgets/expression/score-expression.test.ts @@ -1,11 +1,12 @@ -import {mockStrings} from "../../strings"; - -import {expressionItem3Options} from "./expression.testdata"; import scoreExpression from "./score-expression"; +import {expressionItem3Options} from "./score-expression.testdata"; import * as ExpressionValidator from "./validate-expression"; import type {PerseusExpressionRubric} from "@khanacademy/perseus-score"; +// TODO: remove strings as a param for scorers +const mockStrings = {}; + describe("scoreExpression", () => { it("should be correctly answerable if validation passes", function () { // Arrange diff --git a/packages/perseus-score/src/widgets/expression/score-expression.testdata.ts b/packages/perseus-score/src/widgets/expression/score-expression.testdata.ts new file mode 100644 index 0000000000..ee152ecb48 --- /dev/null +++ b/packages/perseus-score/src/widgets/expression/score-expression.testdata.ts @@ -0,0 +1,36 @@ +import type {PerseusExpressionWidgetOptions} from "@khanacademy/perseus-core"; + +export const expressionItem3Options: PerseusExpressionWidgetOptions = { + answerForms: [ + { + considered: "ungraded", + form: false, + simplify: false, + value: "x+1", + }, + { + considered: "wrong", + form: false, + simplify: false, + value: "y+1", + }, + { + considered: "correct", + form: false, + simplify: false, + value: "z+1", + }, + { + considered: "correct", + form: false, + simplify: false, + value: "a+1", + }, + ], + times: false, + buttonSets: ["basic"], + functions: ["f", "g", "h"], + buttonsVisible: "focused", + visibleLabel: "number of cm", + ariaLabel: "number of centimeters", +}; diff --git a/packages/perseus/src/widgets/expression/score-expression.ts b/packages/perseus-score/src/widgets/expression/score-expression.ts similarity index 93% rename from packages/perseus/src/widgets/expression/score-expression.ts rename to packages/perseus-score/src/widgets/expression/score-expression.ts index aefdfacba8..a43e42a354 100644 --- a/packages/perseus/src/widgets/expression/score-expression.ts +++ b/packages/perseus-score/src/widgets/expression/score-expression.ts @@ -1,21 +1,22 @@ import * as KAS from "@khanacademy/kas"; -import {Errors} from "@khanacademy/perseus-core"; -import {KhanAnswerTypes} from "@khanacademy/perseus-score"; +import { + Errors, + getDecimalSeparator, + PerseusError, +} from "@khanacademy/perseus-core"; import _ from "underscore"; -import {Log} from "../../logging/log"; +import KhanAnswerTypes from "../../util/answer-types"; -import getDecimalSeparator from "./get-decimal-separator"; import validateExpression from "./validate-expression"; -import type {PerseusStrings} from "../../strings"; -import type {PerseusExpressionAnswerForm} from "@khanacademy/perseus-core"; +import type {Score} from "../../util/answer-types"; import type { - PerseusScore, - Score, PerseusExpressionRubric, PerseusExpressionUserInput, -} from "@khanacademy/perseus-score"; + PerseusScore, +} from "../../validation.types"; +import type {PerseusExpressionAnswerForm} from "@khanacademy/perseus-core"; /* Content creators input a list of answers which are matched from top to * bottom. The intent is that they can include spcific solutions which should @@ -38,7 +39,8 @@ import type { function scoreExpression( userInput: PerseusExpressionUserInput, rubric: PerseusExpressionRubric, - strings: PerseusStrings, + // TODO: remove strings as a param for scorers + strings: unknown, locale: string, ): PerseusScore { const validationError = validateExpression(userInput); @@ -62,12 +64,10 @@ function scoreExpression( // in the function variables list for the expression. if (!expression.parsed) { /* c8 ignore next */ - Log.error( + throw new PerseusError( "Unable to parse solution answer for expression", Errors.InvalidInput, - {loggedMetadata: {rubric: JSON.stringify(rubric)}}, ); - return null; } return KhanAnswerTypes.expression.createValidatorFunctional( diff --git a/packages/perseus/src/widgets/expression/validate-expression.test.ts b/packages/perseus-score/src/widgets/expression/validate-expression.test.ts similarity index 100% rename from packages/perseus/src/widgets/expression/validate-expression.test.ts rename to packages/perseus-score/src/widgets/expression/validate-expression.test.ts diff --git a/packages/perseus/src/widgets/expression/validate-expression.ts b/packages/perseus-score/src/widgets/expression/validate-expression.ts similarity index 93% rename from packages/perseus/src/widgets/expression/validate-expression.ts rename to packages/perseus-score/src/widgets/expression/validate-expression.ts index 6c7bda3a6c..3941aae140 100644 --- a/packages/perseus/src/widgets/expression/validate-expression.ts +++ b/packages/perseus-score/src/widgets/expression/validate-expression.ts @@ -1,7 +1,7 @@ import type { - ValidationResult, PerseusExpressionUserInput, -} from "@khanacademy/perseus-score"; + ValidationResult, +} from "../../validation.types"; /** * Checks user input from the expression widget to see if it is scorable. diff --git a/packages/perseus/src/widgets/expression/expression.tsx b/packages/perseus/src/widgets/expression/expression.tsx index ccdeb8ba26..483148acd7 100644 --- a/packages/perseus/src/widgets/expression/expression.tsx +++ b/packages/perseus/src/widgets/expression/expression.tsx @@ -1,6 +1,15 @@ import * as KAS from "@khanacademy/kas"; import {KeyArray, KeypadInput, KeypadType} from "@khanacademy/math-input"; +import { + getDecimalSeparator, + type PerseusExpressionWidgetOptions, +} from "@khanacademy/perseus-core"; import {linterContextDefault} from "@khanacademy/perseus-linter"; +import { + scoreExpression, + type PerseusExpressionRubric, + type PerseusExpressionUserInput, +} from "@khanacademy/perseus-score"; import {View} from "@khanacademy/wonder-blocks-core"; import Tooltip from "@khanacademy/wonder-blocks-tooltip"; import {LabelSmall} from "@khanacademy/wonder-blocks-typography"; @@ -18,18 +27,10 @@ import {ApiOptions, ClassNames as ApiClassNames} from "../../perseus-api"; import a11y from "../../util/a11y"; import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/expression/expression-ai-utils"; -import getDecimalSeparator from "./get-decimal-separator"; -import scoreExpression from "./score-expression"; - import type {DependenciesContext} from "../../dependencies"; import type {FocusPath, Widget, WidgetExports, WidgetProps} from "../../types"; import type {ExpressionPromptJSON} from "../../widget-ai-utils/expression/expression-ai-utils"; import type {Keys as Key, KeypadConfiguration} from "@khanacademy/math-input"; -import type {PerseusExpressionWidgetOptions} from "@khanacademy/perseus-core"; -import type { - PerseusExpressionRubric, - PerseusExpressionUserInput, -} from "@khanacademy/perseus-score"; import type {PropsFor} from "@khanacademy/wonder-blocks-core"; type InputPath = ReadonlyArray;