Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(LEMS-2272): Add scientific notation button to the first tab of expression widget #1738

Merged
merged 14 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/tidy-suns-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@khanacademy/math-input": minor
"@khanacademy/perseus": minor
"@khanacademy/perseus-editor": minor
---

add scientific notation button / toggle to basic keypad
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import type {ClickKeyCallback} from "../../../types";

type Props = {
onClickKey: ClickKeyCallback;
scientific?: boolean;
};

export default function NumbersPage(props: Props) {
const {onClickKey} = props;
export default function NumbersPage({onClickKey, scientific}: Props) {
const {strings} = useMathInputI18n();
const Keys = KeyConfigs(strings);
// These keys are arranged sequentially so that tabbing follows numerical order. This
Expand Down Expand Up @@ -92,6 +92,14 @@ export default function NumbersPage(props: Props) {
coord={[3, 0]}
secondary
/>
{scientific && (
<KeypadButton
keyConfig={Keys.EXP}
onClickKey={onClickKey}
coord={[3, 2]}
secondary
/>
)}
</>
);
}
7 changes: 6 additions & 1 deletion packages/math-input/src/components/keypad/keypad.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export type Props = {
basicRelations?: boolean;
advancedRelations?: boolean;
fractionsOnly?: boolean;
scientific?: boolean;

onClickKey: ClickKeyCallback;
onAnalyticsEvent: AnalyticsEventHandlerFn;
Expand Down Expand Up @@ -89,6 +90,7 @@ export default function Keypad({extraKeys = [], ...props}: Props) {
logarithms,
basicRelations,
advancedRelations,
scientific,
showDismiss,
onAnalyticsEvent,
fractionsOnly,
Expand Down Expand Up @@ -155,7 +157,10 @@ export default function Keypad({extraKeys = [], ...props}: Props) {
/>
)}
{selectedPage === "Numbers" && (
<NumbersPage onClickKey={onClickKey} />
<NumbersPage
onClickKey={onClickKey}
scientific={scientific}
/>
)}
{selectedPage === "Extras" && (
<ExtrasPage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ class MobileKeypadInternals
containerWidth > expandedViewThreshold
}
showDismiss
scientific={
isExpression && keypadConfig?.scientific
}
/>
) : null}
</AphroditeCssTransitionGroup>
Expand Down
1 change: 1 addition & 0 deletions packages/math-input/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export type KeypadConfiguration = {
keypadType: KeypadType;
extraKeys?: ReadonlyArray<Key>;
times?: boolean;
scientific?: boolean;
};

export type KeyHandler = (key: Key) => Cursor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,23 @@ describe("expression-editor", () => {
});
});

it("should toggle scientific checkbox", async () => {
const onChangeMock = jest.fn();

render(<ExpressionEditor onChange={onChangeMock} />);
act(() => jest.runOnlyPendingTimers());

await userEvent.click(
screen.getByRole("checkbox", {
name: "scientific",
}),
);

expect(onChangeMock).toBeCalledWith({
buttonSets: ["basic", "scientific"],
});
});

it("should be possible to add an answer", async () => {
const onChangeMock = jest.fn();

Expand Down
1 change: 1 addition & 0 deletions packages/perseus-editor/src/widgets/expression-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const buttonSetsList: LegacyButtonSets = [
"trig",
"prealgebra",
"logarithms",
"scientific",
"basic relations",
"advanced relations",
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const meta: Meta = {
logarithms: true,
preAlgebra: true,
trigonometry: true,
scientific: true,
},
convertDotToTimes: false,
value: "",
Expand Down
33 changes: 32 additions & 1 deletion packages/perseus/src/components/__tests__/math-input.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ import {testDependencies} from "../../../../../testing/test-dependencies";
import * as Dependencies from "../../dependencies";
import MathInput from "../math-input";

import type {KeypadButtonSets} from "../math-input";
import type {UserEvent} from "@testing-library/user-event";

const allButtonSets = {
const allButtonSets: KeypadButtonSets = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yay! Thanks for adding typing. <3

advancedRelations: true,
basicRelations: true,
divisionKey: true,
logarithms: true,
preAlgebra: true,
trigonometry: true,
scientific: true,
};

describe("Perseus' MathInput", () => {
Expand Down Expand Up @@ -142,6 +144,35 @@ describe("Perseus' MathInput", () => {
expect(mockOnChange).toHaveBeenLastCalledWith("1+2-3");
});

it("is possible to use the scientific keypad", async () => {
// Arrange
const mockOnChange = jest.fn();
render(
<MathInput
onChange={mockOnChange}
keypadButtonSets={{scientific: true}}
onAnalyticsEvent={() => Promise.resolve()}
convertDotToTimes={false}
value=""
/>,
);
act(() => jest.runOnlyPendingTimers());

// Act
await userEvent.click(
screen.getByRole("button", {name: /open math keypad/}),
);
await userEvent.click(screen.getByRole("button", {name: "2"}));
await userEvent.click(
screen.getByRole("button", {name: "Custom exponent"}),
);
await userEvent.click(screen.getByRole("button", {name: "2"}));
act(() => jest.runOnlyPendingTimers());

// Assert
expect(mockOnChange).toHaveBeenLastCalledWith("2^{2}");
});

it("is possible to use buttons with legacy props", async () => {
// Arrange
const mockOnChange = jest.fn();
Expand Down
6 changes: 5 additions & 1 deletion packages/perseus/src/components/math-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,14 @@ import type {AnalyticsEventHandlerFn} from "@khanacademy/perseus-core";

type ButtonsVisibleType = "always" | "never" | "focused";

type KeypadButtonSets = {
export type KeypadButtonSets = {
advancedRelations?: boolean;
basicRelations?: boolean;
divisionKey?: boolean;
logarithms?: boolean;
preAlgebra?: boolean;
trigonometry?: boolean;
scientific?: boolean;
};

type Props = {
Expand Down Expand Up @@ -496,6 +497,9 @@ const mapButtonSets = (buttonSets?: LegacyButtonSets) => {
case "trig":
keypadButtonSets.trigonometry = true;
break;
case "scientific":
keypadButtonSets.scientific = true;
break;
case "basic":
default:
break;
Expand Down
1 change: 1 addition & 0 deletions packages/perseus/src/perseus-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,7 @@ export type LegacyButtonSets = ReadonlyArray<
| "logarithms"
| "basic relations"
| "advanced relations"
| "scientific"
>;

export type PerseusExpressionWidgetOptions = {
Expand Down
82 changes: 80 additions & 2 deletions packages/perseus/src/widgets/expression/expression.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {it, describe, beforeEach} from "@jest/globals";
import {KeypadType} from "@khanacademy/math-input";
import {act, screen, waitFor} from "@testing-library/react";
import {userEvent as userEventLib} from "@testing-library/user-event";

Expand All @@ -12,7 +13,9 @@ import {mockStrings} from "../../strings";
import {scorePerseusItemTesting} from "../../util/test-utils";
import {renderQuestion} from "../__testutils__/renderQuestion";

import ExpressionWidgetExport from "./expression";
import ExpressionWidgetExport, {
keypadConfigurationForProps,
} from "./expression";
import {
expressionItem2,
expressionItem3,
Expand All @@ -21,9 +24,10 @@ import {
} from "./expression.testdata";

import type {
PerseusExpressionWidgetOptions,
PerseusItem,
PerseusExpressionWidgetOptions,
} from "../../perseus-types";
import type {KeypadConfiguration} from "@khanacademy/math-input";
import type {UserEvent} from "@testing-library/user-event";

const renderAndAnswer = async (
Expand Down Expand Up @@ -582,3 +586,77 @@ describe("Expression Widget", function () {
});
});
});

describe("Keypad configuration", () => {
it("should handle basic button set", async () => {
// Arrange
const widgetOptions: PerseusExpressionWidgetOptions = {
answerForms: [],
buttonSets: ["basic"],
times: false,
functions: [],
};

const expected: KeypadConfiguration = {
keypadType: KeypadType.EXPRESSION,
times: false,
extraKeys: ["PI"],
};

// Act
const result = keypadConfigurationForProps(widgetOptions);

// Assert
expect(result).toEqual(expected);
});

it("should handle basic+div button set", async () => {
// Arrange
// Act
// Assert
expect(
keypadConfigurationForProps({
answerForms: [],
buttonSets: ["basic+div"],
times: false,
functions: [],
}),
).toEqual({
keypadType: KeypadType.EXPRESSION,
times: false,
extraKeys: ["PI"],
});
});

it("should return expression keypad configuration by default", async () => {
// Arrange
// Act
const result = keypadConfigurationForProps({
answerForms: [],
buttonSets: [],
times: false,
functions: [],
});

// Assert
expect(result.keypadType).toEqual(KeypadType.EXPRESSION);
});

it("should handle scientific button set", async () => {
// Arrange
// Act
// Assert
expect(
keypadConfigurationForProps({
answerForms: [],
buttonSets: ["scientific"],
times: false,
functions: [],
}),
).toEqual({
keypadType: KeypadType.EXPRESSION,
times: false,
extraKeys: ["PI"],
});
});
});
2 changes: 1 addition & 1 deletion packages/perseus/src/widgets/expression/expression.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ const styles = StyleSheet.create({
* to be included as keys on the keypad. These are scraped from the answer
* forms.
*/
const keypadConfigurationForProps = (
export const keypadConfigurationForProps = (
widgetOptions: PerseusExpressionWidgetOptions,
): KeypadConfiguration => {
// Always use the Expression keypad, regardless of the button sets that have
Expand Down
Loading