Skip to content

Commit

Permalink
merge main into new/bundle
Browse files Browse the repository at this point in the history
  • Loading branch information
jiaojiaojiaoCpbr committed Feb 21, 2024
2 parents 6996212 + 1e17919 commit e15ff45
Show file tree
Hide file tree
Showing 21 changed files with 512 additions and 848 deletions.
6 changes: 6 additions & 0 deletions .changeset/proud-donkeys-sip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@khanacademy/perseus": patch
---

Remove unused code related to KaTeX and MathJax 2. It's no longer needed
because all callers have upgraded to MathJax 3.
5 changes: 5 additions & 0 deletions .changeset/red-beans-dress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/perseus": patch
---

Upgrade MathJax
4 changes: 4 additions & 0 deletions .github/workflows/node-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ jobs:
matchAllGlobs: true # Default is to match any of the globs, which ends up matching all files
conjunctive: true # Only match files that match all of the above

- uses: webfactory/[email protected]
with:
ssh-private-key: ${{ secrets.KHAN_ACTIONS_BOT_SSH_PRIVATE_KEY }}

- name: Verify changeset entries
uses: Khan/[email protected]
with:
Expand Down
13 changes: 13 additions & 0 deletions packages/perseus-editor/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# @khanacademy/perseus-editor

## 2.17.0

### Minor Changes

- [#894](https://github.com/Khan/perseus/pull/894) [`49d5c821`](https://github.com/Khan/perseus/commit/49d5c821a2ad07aadf31f09f2814859de2c0f157) Thanks [@jeremywiebe](https://github.com/jeremywiebe)! - Add a confirmation before deleting a configured widget in the Exercise Editor

* [#896](https://github.com/Khan/perseus/pull/896) [`04981063`](https://github.com/Khan/perseus/commit/049810636968afac5672f790896768338319810a) Thanks [@jeremywiebe](https://github.com/jeremywiebe)! - Add a confirmation before deleting a hint in the Exercise Editor

### Patch Changes

- Updated dependencies [[`a5479339`](https://github.com/Khan/perseus/commit/a547933946b8be33b388fa4654d87289734848f0)]:
- @khanacademy/perseus@17.8.0

## 2.16.2

### Patch Changes
Expand Down
4 changes: 2 additions & 2 deletions packages/perseus-editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"description": "Perseus editors",
"author": "Khan Academy",
"license": "MIT",
"version": "2.16.2",
"version": "2.17.0",
"publishConfig": {
"access": "public"
},
Expand All @@ -25,7 +25,7 @@
"@khanacademy/kas": "^0.3.7",
"@khanacademy/kmath": "^0.1.8",
"@khanacademy/math-input": "^16.5.0",
"@khanacademy/perseus": "^17.7.0",
"@khanacademy/perseus": "^17.8.0",
"@khanacademy/perseus-core": "1.4.1"
},
"devDependencies": {
Expand Down
218 changes: 120 additions & 98 deletions packages/perseus-editor/src/__tests__/editor.test.tsx
Original file line number Diff line number Diff line change
@@ -1,120 +1,142 @@
import {ApiOptions, Dependencies, Widgets} from "@khanacademy/perseus";
import {
ApiOptions,
Dependencies,
Widgets,
widgets,
Util,
} from "@khanacademy/perseus";
import {render, screen} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import * as React from "react";
import "@testing-library/jest-dom"; // Imports custom mathers

import {testDependencies} from "../../../../testing/test-dependencies";
import {wait} from "../../../../testing/wait";
import {question1} from "../__testdata__/input-number.testdata";
import Editor from "../editor";
import ImageEditor from "../widgets/image-editor";

import type {PropsFor} from "@khanacademy/wonder-blocks-core";

const Harnessed = (props: Partial<PropsFor<typeof Editor>>) => {
return (
<Editor
apiOptions={ApiOptions.defaults}
onChange={() => {}}
content="[[☃ image 1]]"
widgets={{
"image 1": {
type: "image",
options: {
backgroundImage: {
url: "http://placekitten.com/200/300",
},
},
},
}}
{...props}
/>
);
};

describe("Editor", () => {
beforeAll(() => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const ImageWidget = widgets.find((w) => w.name === "image")!;
expect(ImageWidget).toBeDefined();
Widgets.registerWidget("image", ImageWidget);
Widgets.registerEditors([ImageEditor]);
});

beforeEach(() => {
jest.spyOn(Dependencies, "getDependencies").mockReturnValue(
testDependencies,
);
});

describe("input-number widget", () => {
beforeEach(async () => {
const [InputNumberWidget, InputNumberEditor] = await Promise.all([
import("@khanacademy/perseus").then((m) => m.InputNumber),
import("../widgets/input-number-editor").then((m) => m.default),
]);
Widgets.registerWidgets([InputNumberWidget]);
Widgets.registerEditors([InputNumberEditor]);
jest.useRealTimers();
it("should delete widget if confirmed", async () => {
const onChangeMock = jest.fn();
jest.spyOn(window, "confirm").mockReturnValue(true);

// Arrange
render(<Harnessed onChange={onChangeMock} />);

// Act
userEvent.click(
screen.getByRole("button", {name: "Remove image widget"}),
);

// Assert
expect(onChangeMock).toHaveBeenCalledWith({content: ""});
});

it("should NOT delete widget if not confirmed", async () => {
const onChangeMock = jest.fn();
jest.spyOn(window, "confirm").mockReturnValue(false);

// Arrange
render(<Harnessed onChange={onChangeMock} />);

// Act
userEvent.click(
screen.getByRole("button", {name: "Remove image widget"}),
);

// Assert
expect(onChangeMock).not.toHaveBeenCalled();
});

test("clicking on the widget editor should open it", async () => {
// Arrange
render(<Harnessed />);

// Act
const widgetDisclosure = screen.getByRole("link", {
name: "image 1",
});
userEvent.click(widgetDisclosure);

// Assert
const previewImage = screen.getByAltText("Editor preview of image");
expect(previewImage).toHaveAttribute(
"src",
"http://placekitten.com/200/300",
);
});

it("should update values", async () => {
// Arrange
jest.spyOn(Util, "getImageSizeModern").mockResolvedValue([200, 200]);

test("clicking on the widget editor should open it", async () => {
// Arrange
render(
<Editor
apiOptions={ApiOptions.defaults}
content={question1.content}
placeholder=""
widgets={question1.widgets}
images={question1.images}
disabled={false}
widgetEnabled={true}
immutableWidgets={false}
showWordCount={true}
warnNoPrompt={true}
warnNoWidgets={true}
onChange={(props) => {}}
/>,
);
await wait();

// Act
const widgetDisclosure = screen.getByRole("link", {
name: "input-number 1",
});
userEvent.click(widgetDisclosure);
const correctAnswerInput = screen.getByLabelText("Correct answer:");

// Assert
expect(correctAnswerInput).toHaveValue("0.5");
const changeFn = jest.fn();
render(<Harnessed onChange={changeFn} />);

// Act
const widgetDisclosure = screen.getByRole("link", {
name: "image 1",
});
userEvent.click(widgetDisclosure);

it("should update values", async () => {
// Arrange
const changeFn = jest.fn();
render(
<Editor
apiOptions={ApiOptions.defaults}
content={question1.content}
placeholder=""
widgets={question1.widgets}
images={question1.images}
disabled={false}
widgetEnabled={true}
immutableWidgets={false}
showWordCount={true}
warnNoPrompt={true}
warnNoWidgets={true}
onChange={changeFn}
/>,
);
await wait();

// Act
const widgetDisclosure = screen.getByRole("link", {
name: "input-number 1",
});
userEvent.click(widgetDisclosure);
const correctAnswerInput = screen.getByLabelText("Correct answer:");

userEvent.clear(correctAnswerInput);
userEvent.paste(correctAnswerInput, "0.75");
userEvent.tab(); // blurring the input triggers onChange to be called

// Assert
expect(changeFn).toHaveBeenCalledWith(
{
widgets: {
"input-number 1": {
graded: true,
version: {major: 0, minor: 0},
static: false,
type: "input-number",
options: {
value: 0.75,
simplify: "required",
size: "normal",
inexact: false,
maxError: 0.1,
answerType: "number",
rightAlign: false,
},
alignment: "default",
},
},
const captionInput = screen.getByLabelText(/Caption:/);

userEvent.clear(captionInput);
userEvent.type(captionInput, "A picture of kittens");
userEvent.tab(); // blurring the input triggers onChange to be called
jest.runOnlyPendingTimers();

// Assert
expect(changeFn).toHaveBeenCalledWith(
{
widgets: {
"image 1": expect.objectContaining({
type: "image",
graded: true,
options: expect.objectContaining({
caption: "A picture of kittens",
}),
}),
},
undefined,
undefined,
);
});
},
undefined,
undefined,
);
});
});
83 changes: 83 additions & 0 deletions packages/perseus-editor/src/__tests__/hint-editor.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import "@testing-library/jest-dom";
import {render, screen} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import * as React from "react";

import CombinedHintsEditor from "../hint-editor";

describe("CombinedHintsEditor", () => {
it("should render", () => {
render(
<CombinedHintsEditor deviceType="phone" previewURL="about:blank" />,
);
});

it("should confirm before removing a hint", () => {
// Arrange
const comfirmSpy = jest.spyOn(window, "confirm").mockReturnValue(false);
render(
<CombinedHintsEditor
deviceType="phone"
previewURL="about:blank"
hints={[
{content: "You know this one!", widgets: {}, images: {}},
{content: "Ok, the answer is 3", widgets: {}, images: {}},
]}
/>,
);

// Act
userEvent.click(screen.getAllByText("Remove this hint")[0]);

// Assert
expect(comfirmSpy).toHaveBeenCalled();
});

it("should not remove the hint if not confirmed", () => {
// Arrange
jest.spyOn(window, "confirm").mockReturnValue(false);
const onChangeMock = jest.fn();
render(
<CombinedHintsEditor
deviceType="phone"
previewURL="about:blank"
hints={[
{content: "You know this one!", widgets: {}, images: {}},
{content: "Ok, the answer is 3", widgets: {}, images: {}},
]}
onChange={onChangeMock}
/>,
);

// Act
userEvent.click(screen.getAllByText("Remove this hint")[0]);

// Assert
expect(onChangeMock).not.toHaveBeenCalled();
});

it("should remove the hint if confirmed", () => {
// Arrange
jest.spyOn(window, "confirm").mockReturnValue(true);
const onChangeMock = jest.fn();
render(
<CombinedHintsEditor
deviceType="phone"
previewURL="about:blank"
hints={[
{content: "You know this one!", widgets: {}, images: {}},
{content: "Ok, the answer is 3", widgets: {}, images: {}},
]}
onChange={onChangeMock}
/>,
);

// Act
userEvent.click(screen.getAllByText("Remove this hint")[0]);

// Assert
expect(onChangeMock).toHaveBeenCalledWith({
hints: [{content: "Ok, the answer is 3", widgets: {}, images: {}}],
});
});
});
Loading

0 comments on commit e15ff45

Please sign in to comment.