Skip to content

Commit

Permalink
[tb/LEMS-2597/dropdown-split-out-validation] Merge branch 'main' into…
Browse files Browse the repository at this point in the history
… tb/LEMS-2597/dropdown-split-out-validation

# Conflicts:
#	packages/perseus-core/src/index.ts
  • Loading branch information
Myranae committed Jan 30, 2025
2 parents 8a6e333 + c72166c commit 781fa9b
Show file tree
Hide file tree
Showing 22 changed files with 383 additions and 43 deletions.
5 changes: 5 additions & 0 deletions .changeset/curly-bikes-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/kmath": patch
---

Update repository information
6 changes: 6 additions & 0 deletions .changeset/forty-meals-teach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@khanacademy/perseus": minor
"@khanacademy/perseus-core": minor
---

Create a function to get the public options for the LabelImage widget
6 changes: 6 additions & 0 deletions .changeset/honest-parents-press.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@khanacademy/perseus": minor
"@khanacademy/perseus-editor": minor
---

[Interactive Graph] Allow axis tick labels to be multiples of pi
5 changes: 5 additions & 0 deletions .changeset/poor-masks-return.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/perseus": major
---

Removes the following deprecated exports from `@khanacademy/perseus`: `parsePerseusItem`, `parseAndMigratePerseusItem`, `parseAndMigratePerseusArticle`, `isSuccess`, `isFailure`, `Result`, `Success`, `Failure`. Clients should import these from `@khanacademy/perseus-core` instead.
5 changes: 3 additions & 2 deletions packages/kmath/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
},
"repository": {
"type": "git",
"url": "https://github.com/Khan/kmath.git"
"url": "https://github.com/Khan/perseus.git",
"directory": "packages/kmath"
},
"bugs": {
"url": "https://github.com/Khan/perseus/issues"
Expand All @@ -35,4 +36,4 @@
"underscore": "1.4.4"
},
"keywords": []
}
}
1 change: 1 addition & 0 deletions packages/perseus-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,5 @@ export type * from "./widgets/logic-export.types";
export {default as getOrdererPublicWidgetOptions} from "./widgets/orderer/orderer-util";
export {default as getCategorizerPublicWidgetOptions} from "./widgets/categorizer/categorizer-util";
export {default as getExpressionPublicWidgetOptions} from "./widgets/expression/expression-util";
export {default as getLabelImagePublicWidgetOptions} from "./widgets/label-image/label-image-util";
export {default as getDropdownPublicWidgetOptions} from "./widgets/dropdown/dropdown-util";
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import getLabelImagePublicWidgetOptions from "./label-image-util";

import type {PerseusLabelImageWidgetOptions} from "@khanacademy/perseus-core";

describe("getLabelImagePublicWidgetOptions", () => {
it("removes private fields", () => {
const options: PerseusLabelImageWidgetOptions = {
choices: ["right", "wrong"],
markers: [
{
answers: ["right"],
label: "",
x: 0,
y: 0,
},
],
imageUrl: "",
imageHeight: 0,
imageWidth: 0,
imageAlt: "",
hideChoicesFromInstructions: false,
multipleAnswers: false,
static: false,
};

const publicWidgetOptions = getLabelImagePublicWidgetOptions(options);

expect(publicWidgetOptions).toEqual({
choices: ["right", "wrong"],
markers: [
{
label: "",
x: 0,
y: 0,
},
],
imageUrl: "",
imageHeight: 0,
imageWidth: 0,
imageAlt: "",
hideChoicesFromInstructions: false,
multipleAnswers: false,
static: false,
});
});
});
41 changes: 41 additions & 0 deletions packages/perseus-core/src/widgets/label-image/label-image-util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type {
PerseusLabelImageMarker,
PerseusLabelImageWidgetOptions,
} from "@khanacademy/perseus-core";

/**
* For details on the individual options, see the
* PerseusLabelImageWidgetOptions type
*/
type LabelImagePublicWidgetOptions = {
choices: PerseusLabelImageWidgetOptions["choices"];
imageUrl: PerseusLabelImageWidgetOptions["imageUrl"];
imageAlt: PerseusLabelImageWidgetOptions["imageAlt"];
imageHeight: PerseusLabelImageWidgetOptions["imageHeight"];
imageWidth: PerseusLabelImageWidgetOptions["imageWidth"];
markers: ReadonlyArray<LabelImageMarkerPublicData>;
hideChoicesFromInstructions: PerseusLabelImageWidgetOptions["hideChoicesFromInstructions"];
multipleAnswers: PerseusLabelImageWidgetOptions["multipleAnswers"];
static: PerseusLabelImageWidgetOptions["static"];
};

type LabelImageMarkerPublicData = Pick<
PerseusLabelImageMarker,
"x" | "y" | "label"
>;

export default function getLabelImagePublicWidgetOptions(
options: PerseusLabelImageWidgetOptions,
): LabelImagePublicWidgetOptions {
return {
...options,
markers: options.markers.map(getLabelImageMarkerPublicData),
};
}

function getLabelImageMarkerPublicData(
marker: PerseusLabelImageMarker,
): LabelImageMarkerPublicData {
const {answers: _, ...publicData} = marker;
return publicData;
}
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,7 @@ class InteractiveGraphSettings extends React.Component<Props, State> {
onChange={(vals) =>
this.changeRange(0, vals)
}
allowPiTruncation={true}
/>
</LabeledRow>
</div>
Expand All @@ -505,6 +506,7 @@ class InteractiveGraphSettings extends React.Component<Props, State> {
onChange={(vals) =>
this.changeRange(1, vals)
}
allowPiTruncation={true}
/>
</LabeledRow>
</div>
Expand All @@ -515,6 +517,7 @@ class InteractiveGraphSettings extends React.Component<Props, State> {
<RangeInput
value={this.state.stepTextbox}
onChange={this.changeStep}
allowPiTruncation={true}
/>
</LabeledRow>
</div>
Expand All @@ -523,6 +526,7 @@ class InteractiveGraphSettings extends React.Component<Props, State> {
<RangeInput
value={this.state.gridStepTextbox}
onChange={this.changeGridStep}
allowPiTruncation={true}
/>
</LabeledRow>
</div>
Expand All @@ -533,6 +537,7 @@ class InteractiveGraphSettings extends React.Component<Props, State> {
<RangeInput
value={this.state.snapStepTextbox}
onChange={this.changeSnapStep}
allowPiTruncation={true}
/>
</LabeledRow>
</div>
Expand Down
43 changes: 43 additions & 0 deletions packages/perseus/src/components/__tests__/number-input.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,47 @@ describe("NumberInput", function () {

expect(onChange).not.toHaveBeenCalled();
});

it.each`
value | expected
${Math.PI} | ${"π"}
${Math.PI * 2} | ${"2π"}
${Math.PI * 0.5} | ${"π/2"}
`(
"allowPiTruncation: loads $value as $expected on mount",
async function ({value, expected}) {
// Arrange

// Act
render(
<NumberInput
value={value}
onChange={jest.fn()}
allowPiTruncation={true}
/>,
);
const input = screen.getByRole("textbox");

// Assert
expect(input).toHaveValue(expected);
},
);

it("does not auto-convert number to pi format when allowPiTruncation is false", async function () {
// Arrange

// Act
render(
<NumberInput
value={Math.PI}
onChange={jest.fn()}
allowPiTruncation={false}
/>,
);
const input = screen.getByRole("textbox");

// Assert
expect(input).not.toHaveValue("π");
expect(input).toHaveValue(Math.PI.toString());
});
});
14 changes: 14 additions & 0 deletions packages/perseus/src/components/number-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as React from "react";
import _ from "underscore";

import Util from "../util";
import {isPiMultiple} from "../util/math-utils";

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

Expand Down Expand Up @@ -48,6 +49,7 @@ class NumberInput extends React.Component<any, any> {
checkValidity: PropTypes.func,
size: PropTypes.oneOf(["mini", "small", "normal"]),
label: PropTypes.oneOf(["put your labels outside your inputs!"]),
allowPiTruncation: PropTypes.bool,
};

static defaultProps: any = {
Expand All @@ -63,6 +65,18 @@ class NumberInput extends React.Component<any, any> {
format: this.props.format,
};

componentDidMount() {
// If the value is a multiple of pi, but it is not in the pi format,
// then convert it to the pi format and show it as a multiple of pi.
const value = this.getValue();
if (this.props.allowPiTruncation && value !== null && value !== 0) {
if (this.state.format !== "pi" && isPiMultiple(value)) {
this._setValue(value / Math.PI, "pi");
this.setState({format: "pi"});
}
}
}

componentDidUpdate(prevProps: any) {
if (!knumber.equal(this.getValue(), this.props.value)) {
this._setValue(this.props.value, this.state.format);
Expand Down
3 changes: 3 additions & 0 deletions packages/perseus/src/components/range-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class RangeInput extends React.Component<any> {
onChange: PropTypes.func.isRequired,
placeholder: PropTypes.array,
checkValidity: PropTypes.func,
allowPiTruncation: PropTypes.bool,
};

static defaultProps: any = {
Expand Down Expand Up @@ -43,6 +44,7 @@ class RangeInput extends React.Component<any> {
// eslint-disable-next-line react/jsx-no-bind
onChange={this.onChange.bind(this, 0)}
placeholder={this.props.placeholder[0]}
allowPiTruncation={this.props.allowPiTruncation}
/>
<NumberInput
{...this.props}
Expand All @@ -51,6 +53,7 @@ class RangeInput extends React.Component<any> {
// eslint-disable-next-line react/jsx-no-bind
onChange={this.onChange.bind(this, 1)}
placeholder={this.props.placeholder[1]}
allowPiTruncation={this.props.allowPiTruncation}
/>
</div>
);
Expand Down
36 changes: 0 additions & 36 deletions packages/perseus/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,28 +108,6 @@ export {
getAnswerFromUserInput,
getImagesWithoutAltData,
} from "./util/extract-perseus-data";
export {
/**
* @deprecated - import this function from perseus-core instead
*/
parsePerseusItem,
/**
* @deprecated - import this function from perseus-core instead
*/
parseAndMigratePerseusItem,
/**
* @deprecated - import this function from perseus-core instead
*/
parseAndMigratePerseusArticle,
/**
* @deprecated - import this function from perseus-core instead
*/
isSuccess,
/**
* @deprecated - import this function from perseus-core instead
*/
isFailure,
} from "@khanacademy/perseus-core";

export {
generateTestPerseusItem,
Expand Down Expand Up @@ -203,20 +181,6 @@ export type {
SharedRendererProps,
} from "./types";
export type {ParsedValue} from "./util";
export type {
/**
* @deprecated - import this function from perseus-core instead
*/
Result,
/**
* @deprecated - import this function from perseus-core instead
*/
Success,
/**
* @deprecated - import this function from perseus-core instead
*/
Failure,
} from "@khanacademy/perseus-core";
export type {Coord} from "./interactive2/types";
export type {
RendererPromptJSON,
Expand Down
4 changes: 3 additions & 1 deletion packages/perseus/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {SizeClass} from "./util/sizing-utils";
import type {WidgetPromptJSON} from "./widget-ai-utils/prompt-types";
import type {KeypadAPI} from "@khanacademy/math-input";
import type {
getLabelImagePublicWidgetOptions,
Hint,
PerseusAnswerArea,
PerseusGraphType,
Expand Down Expand Up @@ -545,7 +546,8 @@ export type PublicWidgetOptionsFunction =
| typeof getDropdownPublicWidgetOptions
| typeof getCategorizerPublicWidgetOptions
| typeof getOrdererPublicWidgetOptions
| typeof getExpressionPublicWidgetOptions;
| typeof getExpressionPublicWidgetOptions
| typeof getLabelImagePublicWidgetOptions;

export type WidgetExports<
T extends React.ComponentType<any> & Widget = React.ComponentType<any>,
Expand Down
32 changes: 32 additions & 0 deletions packages/perseus/src/util/math-utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {isPiMultiple} from "./math-utils";

describe("isPiMultiple", () => {
test.each`
case | number
${"π"} | ${Math.PI}
${"2π"} | ${Math.PI * 2}
${"3π"} | ${Math.PI * 3}
${"-π"} | ${Math.PI * -1}
${"-2π"} | ${Math.PI * -2}
${"π/2"} | ${Math.PI / 2}
${"π/3"} | ${Math.PI / 3}
${"π/4"} | ${Math.PI / 4}
${"π/6"} | ${Math.PI / 6}
${"2π/3"} | ${(Math.PI * 2) / 3}
`("should return true for $case", ({number}) => {
expect(isPiMultiple(number)).toBe(true);
});

test.each`
case | number
${"0"} | ${0}
${"1"} | ${1}
${"-1"} | ${-1}
${"3.14"} | ${3.14}
${"3.14159"} | ${3.14159}
${"2.5"} | ${2.5}
${"-1.5"} | ${-1.5}
`("should return false for $case", ({number}) => {
expect(isPiMultiple(number)).toBe(false);
});
});
Loading

0 comments on commit 781fa9b

Please sign in to comment.