Skip to content

Commit

Permalink
[LEMS-2852] Answerless Expression (#2226)
Browse files Browse the repository at this point in the history
## Summary:
So the main thing with the Expression widget is that it was using answer data to determine which `extraKeys` to show in the `MathInput` keypad. For instance if a possible answer was `42i`, it would show `i` in the `Extra` tab on the keypad.

This PR sets us up for removing answers by adding `extraKeys` to the data schema so it can be determined at publish time rather than read time. We simulate this by upgrading the widget to provide `extraKeys` when not present on the data.

Issue: LEMS-2852

## Test plan:
- Go to an Expression widget that has a constant/variable in the answer
- It should render the same, including having the constant/variable in the keypad
- It should be answerable/scorable

Author: handeyeco

Reviewers: handeyeco, jeremywiebe

Required Reviewers:

Approved By: jeremywiebe

Checks: ✅ 8 checks were successful

Pull Request URL: #2226
  • Loading branch information
handeyeco authored Mar 5, 2025
1 parent a0aee41 commit 909148c
Show file tree
Hide file tree
Showing 48 changed files with 1,055 additions and 445 deletions.
8 changes: 8 additions & 0 deletions .changeset/three-teachers-punch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@khanacademy/math-input": major
"@khanacademy/perseus-core": major
"@khanacademy/perseus-editor": minor
"@khanacademy/perseus": patch
---

Answerless Expression: Expression can render and is interactive with answerless data
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,16 @@ import {userEvent as userEventLib} from "@testing-library/user-event";
import MathQuill from "mathquill";
import React, {useState} from "react";

import {KeypadType} from "../../enums";
import MathInput from "../input/math-input";
import {MobileKeypad} from "../keypad";

import type {KeypadConfiguration} from "../../types";
import type {KeypadConfiguration} from "@khanacademy/perseus-core";
import type {UserEvent} from "@testing-library/user-event";

const MQ = MathQuill.getInterface(3);

const defaultConfiguration: KeypadConfiguration = {
keypadType: KeypadType.FRACTION,
keypadType: "FRACTION",
};

function InputWithContext({keypadConfiguration}) {
Expand Down Expand Up @@ -249,8 +248,8 @@ describe("math input integration", () => {
});

it("handles fractions correctly in expression", async () => {
const keypadConfiguration = {
keypadType: KeypadType.EXPRESSION,
const keypadConfiguration: KeypadConfiguration = {
keypadType: "EXPRESSION",
};
render(
<ConnectedMathInput keypadConfiguration={keypadConfiguration} />,
Expand Down
6 changes: 3 additions & 3 deletions packages/math-input/src/components/input/math-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import type {
MathFieldInterface,
MathFieldUpdaterCallback,
} from "./mathquill-types";
import type Key from "../../data/keys";
import type {MathInputStrings} from "../../strings";
import type {KeypadKey} from "@khanacademy/perseus-core";

/**
* This file contains a wrapper around MathQuill so that we can provide a
Expand All @@ -35,7 +35,7 @@ import type {MathInputStrings} from "../../strings";
class MathWrapper {
mathField: MathFieldInterface; // MathQuill MathField input
callbacks: any;
mobileKeyTranslator: Record<Key, MathFieldUpdaterCallback>;
mobileKeyTranslator: Record<KeypadKey, MathFieldUpdaterCallback>;

constructor(
mathFieldMount,
Expand Down Expand Up @@ -97,7 +97,7 @@ class MathWrapper {
* @param {Key} key - an enum representing the key that was pressed
* @returns {object} a cursor object, consisting of a cursor context
*/
pressKey(key: Key) {
pressKey(key: KeypadKey) {
const cursor = this.getCursor();
const translator = this.mobileKeyTranslator[key];

Expand Down
4 changes: 2 additions & 2 deletions packages/math-input/src/components/input/mathquill-types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type Key from "../../data/keys";
import type {KeypadKey} from "@khanacademy/perseus-core";
import type MathQuill from "mathquill";

export type MathQuillInterface = MathQuill.v3.API;
Expand All @@ -17,7 +17,7 @@ export type MathFieldInterface = MathQuill.v3.EditableMathQuill & {

export type MathFieldUpdaterCallback = (
mathField: MathFieldInterface,
key: Key,
key: KeypadKey,
) => void;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {
} from "../input/mathquill-helpers";
import {mathQuillInstance} from "../input/mathquill-instance";

import type Key from "../../data/keys";
import type {MathFieldInterface} from "../input/mathquill-types";
import type {KeypadKey} from "@khanacademy/perseus-core";
import type MathQuill from "mathquill";

function handleLeftArrow(
Expand Down Expand Up @@ -57,7 +57,10 @@ function handleRightArrow(
}
}

export default function handleArrow(mathField: MathFieldInterface, key: Key) {
export default function handleArrow(
mathField: MathFieldInterface,
key: KeypadKey,
) {
const cursor = mathField.cursor();

if (key === "LEFT") {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import {MathFieldActionType} from "../../types";
import {mathQuillInstance} from "../input/mathquill-instance";

import type Key from "../../data/keys";
import type {MathFieldInterface} from "../input/mathquill-types";
import type {KeypadKey} from "@khanacademy/perseus-core";

const ArithmeticOperators = ["+", "-", "\\cdot", "\\times", "\\div"];
const EqualityOperators = ["=", "\\neq", "<", "\\leq", ">", "\\geq"];

export default function handleExponent(
mathField: MathFieldInterface,
key: Key,
key: KeypadKey,
) {
const cursor = mathField.cursor();
// If there's an invalid operator preceding the cursor (anything that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import {
} from "../input/mathquill-helpers";
import {mathQuillInstance} from "../input/mathquill-instance";

import type Key from "../../data/keys";
import type {MathFieldInterface} from "../input/mathquill-types";
import type {KeypadKey} from "@khanacademy/perseus-core";

const KeysForJumpContext = {
[CursorContext.IN_PARENS]: "JUMP_OUT_PARENTHESES",
Expand All @@ -22,7 +22,7 @@ const KeysForJumpContext = {
/**
* Advances the cursor to the next logical position.
*/
function handleJumpOut(mathField: MathFieldInterface, key: Key): void {
function handleJumpOut(mathField: MathFieldInterface, key: KeypadKey): void {
const cursor = mathField.cursor();
const context = getCursorContext(mathField);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import handleArrow from "./handle-arrow";
import handleExponent from "./handle-exponent";
import handleJumpOut from "./handle-jump-out";

import type Key from "../../data/keys";
import type {
MathFieldInterface,
MathFieldUpdaterCallback,
} from "../input/mathquill-types";
import type {KeypadKey} from "@khanacademy/perseus-core";

function buildGenericCallback(
str: string,
Expand Down Expand Up @@ -75,7 +75,7 @@ type KeyTranslatorStrings = {
export const getKeyTranslator = (
locale: string,
strings: KeyTranslatorStrings,
): Record<Key, MathFieldUpdaterCallback> => ({
): Record<KeypadKey, MathFieldUpdaterCallback> => ({
EXP: handleExponent,
EXP_2: handleExponent,
EXP_3: handleExponent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import {createMathField} from "../../input/mathquill-instance";
import {getKeyTranslator} from "../../key-handlers/key-translator";
import Keypad from "../index";

import type Key from "../../../data/keys";
import type {MathFieldInterface} from "../../input/mathquill-types";
import type {AnalyticsEventHandlerFn} from "@khanacademy/perseus-core";
import type {
AnalyticsEventHandlerFn,
KeypadKey,
} from "@khanacademy/perseus-core";
import type {UserEvent} from "@testing-library/user-event";

type Props = {
Expand Down Expand Up @@ -59,7 +61,7 @@ function V2KeypadWithMathquill(props: Props) {

const keyTranslator = getKeyTranslator("en", strings);

function handleClickKey(key: Key) {
function handleClickKey(key: KeypadKey) {
if (!mathField) {
return;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/math-input/src/components/keypad/button-assets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import * as React from "react";

import {useMathInputI18n} from "../i18n-context";

import type Key from "../../data/keys";
import type {KeypadKey} from "@khanacademy/perseus-core";

type Props = {id: Key};
type Props = {id: KeypadKey};

export default function ButtonAsset({id}: Props): React.ReactNode {
const {locale, strings} = useMathInputI18n();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {getCursorContext} from "../input/mathquill-helpers";
import {createMathField} from "../input/mathquill-instance";
import {getKeyTranslator} from "../key-handlers/key-translator";

import type Key from "../../data/keys";
import type {MathFieldInterface} from "../input/mathquill-types";
import type {KeypadKey} from "@khanacademy/perseus-core";

import Keypad from "./index";

Expand Down Expand Up @@ -52,7 +52,7 @@ export function V2KeypadWithMathquill() {
tan: "tan",
});

function handleClickKey(key: Key) {
function handleClickKey(key: KeypadKey) {
if (!mathField) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import KeyConfigs from "../../../data/key-configs";
import {useMathInputI18n} from "../../i18n-context";
import {KeypadButton} from "../keypad-button";

import type Key from "../../../data/keys";
import type {ClickKeyCallback} from "../../../types";
import type {KeypadKey} from "@khanacademy/perseus-core";

type Props = {
extraKeys: ReadonlyArray<Key>;
extraKeys: ReadonlyArray<KeypadKey>;
onClickKey: ClickKeyCallback;
};

Expand Down
8 changes: 5 additions & 3 deletions packages/math-input/src/components/keypad/keypad.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ import NavigationPad from "./navigation-pad";
import SharedKeys from "./shared-keys";
import {expandedViewThreshold} from "./utils";

import type Key from "../../data/keys";
import type {ClickKeyCallback, KeypadPageType} from "../../types";
import type {CursorContext} from "../input/cursor-contexts";
import type {AnalyticsEventHandlerFn} from "@khanacademy/perseus-core";
import type {
AnalyticsEventHandlerFn,
KeypadKey,
} from "@khanacademy/perseus-core";

type Props = {
extraKeys?: ReadonlyArray<Key>;
extraKeys?: ReadonlyArray<KeypadKey>;
cursorContext?: (typeof CursorContext)[keyof typeof CursorContext];
showDismiss?: boolean;
expandedView?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,12 @@ import AphroditeCssTransitionGroup from "../aphrodite-css-transition-group";
import Keypad from "./keypad";
import {expandedViewThreshold} from "./utils";

import type Key from "../../data/keys";
import type {Cursor, KeyHandler, KeypadAPI} from "../../types";
import type {
Cursor,
AnalyticsEventHandlerFn,
KeypadConfiguration,
KeyHandler,
KeypadAPI,
} from "../../types";
import type {AnalyticsEventHandlerFn} from "@khanacademy/perseus-core";
KeypadKey,
} from "@khanacademy/perseus-core";
import type {StyleType} from "@khanacademy/wonder-blocks-core";

const AnimationDurationInMS = 200;
Expand Down Expand Up @@ -157,7 +155,7 @@ class MobileKeypadInternals
return ReactDOM.findDOMNode(this);
};

_handleClickKey(key: Key) {
_handleClickKey(key: KeypadKey) {
if (key === "DISMISS") {
this.dismiss();
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import * as React from "react";

import ButtonAsset from "./button-assets";

import type Key from "../../data/keys";
import type {KeyConfig, ClickKeyCallback} from "../../types";
import type {KeypadKey} from "@khanacademy/perseus-core";

type KeypadButtonProps = {
// 0 indexed [x, y] position in keypad CSS grid
Expand All @@ -16,7 +16,7 @@ type KeypadButtonProps = {
onClickKey: ClickKeyCallback;
};

function getStyles(key: Key) {
function getStyles(key: KeypadKey) {
switch (key) {
case "UP":
return styles.up;
Expand Down
7 changes: 4 additions & 3 deletions packages/math-input/src/data/key-configs.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
/**
* This file contains configuration settings for the buttons in the keypad.
*/
import type Key from "./keys";

import type {KeyType} from "../enums";
import type {MathInputStrings} from "../strings";
import type {KeyConfig} from "../types";
import type {KeypadKey} from "@khanacademy/perseus-core";

type KeyConfigMapper = (args: {
key: Key;
key: KeypadKey;
keyType?: KeyType;
ariaLabel?: string;
data?: string;
Expand Down Expand Up @@ -58,7 +59,7 @@ const getDefaultNumberFields: KeyConfigMapper = ({
const KeyConfigs = (
strings: MathInputStrings,
): {
[key in Key]: KeyConfig;
[key in KeypadKey]: KeyConfig;
} => ({
// Basic math
PLUS: {
Expand Down
Loading

0 comments on commit 909148c

Please sign in to comment.