Skip to content

Commit

Permalink
[Interactive Graph] Add example function area for locked function set…
Browse files Browse the repository at this point in the history
…tings (#1600)

## Summary:
Having a list of sample functions available to quickly copy/paste into the locked function settings can be very helpful for content creators. This PR adds that capability, along with an updatable file (locked-function-examples.ts) for adding/modifying example functions.


Issue: LEMS-1948

## Test plan:

1. Launch Storybook (yarn start)
2. Navigate to Perseus Editor => Editor => [Demo Interactive Graph](http://localhost:6006/?path=/story/perseuseditor-editor--demo-interactive-graph)
3. Open the "interactive-graph 1" widget settings
4. Click on the "Add locked figure" drop-down at the bottom of the editor and choose "Function"
5. Below the "domain min/max" fields there is an "examples" dropdown, and instructions to the right.
6. Choose an example category from the dropdown.
   * Note that the instructions are replaced by examples for that category.
   * Currently, there are only 2 examples per category, but if you edit the DOM and add another list item, you can see that the area's height is limited, and you can scroll to see the additional example(s).
7. Copy one of the examples by clicking on the copy icon to the left of the example that you want.
8. Paste the contents of the clipboard into the equation field (above the domain min/max section).

## Affected UI
### New settings feature
![Example Equations - Updated](https://github.com/user-attachments/assets/473382e8-72eb-443d-aa61-4585f8da96c9)

Author: mark-fitzgerald

Reviewers: nishasy, mark-fitzgerald, #perseus

Required Reviewers:

Approved By: nishasy

Checks: ❌ codecov/project, ✅ codecov/patch, ✅ Upload Coverage (ubuntu-latest, 20.x), ✅ Jest Coverage (ubuntu-latest, 20.x), ✅ Publish npm snapshot (ubuntu-latest, 20.x), ✅ Cypress (ubuntu-latest, 20.x), ✅ Lint, Typecheck, Format, and Test (ubuntu-latest, 20.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ✅ Publish Storybook to Chromatic (ubuntu-latest, 20.x), ✅ Check builds for changes in size (ubuntu-latest, 20.x), ✅ gerald

Pull Request URL: #1600
  • Loading branch information
mark-fitzgerald authored Sep 10, 2024
1 parent 08068dc commit bdb382f
Show file tree
Hide file tree
Showing 4 changed files with 309 additions and 23 deletions.
5 changes: 5 additions & 0 deletions .changeset/few-boats-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/perseus-editor": minor
---

Interactive Graph - Add example functions for copy/paste to locked functions settings
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import {render, screen} from "@testing-library/react";
import {userEvent as userEventLib} from "@testing-library/user-event";
import * as React from "react";

// Disabling the following linting error because the import is needed for mocking purposes.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import examples from "../graph-locked-figures/locked-function-examples";
import LockedFunctionSettings from "../graph-locked-figures/locked-function-settings";
import {getDefaultFigureForType} from "../util";

Expand All @@ -16,6 +19,14 @@ const defaultProps = {
onRemove: () => {},
} as Props;

const exampleEquationsMock = {
foo: ["bar", "zot"],
};
jest.mock("../graph-locked-figures/locked-function-examples", () => ({
__esModule: true,
default: exampleEquationsMock,
}));

describe("Locked Function Settings", () => {
let userEvent: UserEvent;
const onChangeProps = jest.fn();
Expand Down Expand Up @@ -222,7 +233,6 @@ describe("Locked Function Settings", () => {

test("calls 'onChangeProps' when directional axis is changed", async () => {
// Arrange
const onChangeProps = jest.fn();
render(
<LockedFunctionSettings
{...defaultProps}
Expand All @@ -244,7 +254,7 @@ describe("Locked Function Settings", () => {
expect(onChangeProps).toHaveBeenCalledWith({directionalAxis: "y"});
});

describe("Domain interactions", () => {
describe("Domain/Range interactions", () => {
test("valid entries update component properties", async () => {
// Arrange
render(
Expand Down Expand Up @@ -363,6 +373,138 @@ describe("Locked Function Settings", () => {
domain: [3, Infinity],
});
});

test("restriction labels reflect the directional axis specified", async () => {
// Arrange
render(
<LockedFunctionSettings
{...defaultProps}
directionalAxis="x"
expanded={true}
onChangeProps={onChangeProps}
/>,
{wrapper: RenderStateRoot},
);

// Assert - "x" axis means "domain"
let minField = screen.getByText("domain min");
expect(minField).toBeInTheDocument();

// Arrange
render(
<LockedFunctionSettings
{...defaultProps}
directionalAxis="y"
expanded={true}
onChangeProps={onChangeProps}
/>,
{wrapper: RenderStateRoot},
);

// Assert - "y" axis means "range"
minField = screen.getByText("range min");
expect(minField).toBeInTheDocument();
});
});

describe("Example equation interactions", () => {
test("shows example equations based upon the category chosen", async () => {
// Arrange
render(
<LockedFunctionSettings
{...defaultProps}
expanded={true}
onChangeProps={onChangeProps}
/>,
{wrapper: RenderStateRoot},
);

// Assert - initial state (no category selected)
let copyButtons = screen.queryAllByLabelText("copy example");
expect(copyButtons.length).toEqual(0);

// Act - choose a category of examples
const categoryDropdown =
screen.getByLabelText("Choose a category");
await userEvent.click(categoryDropdown);
const categoryOption = screen.getAllByRole("option")[0];
await userEvent.click(categoryOption);

// Assert - modified state
copyButtons = screen.queryAllByLabelText("copy example");
expect(copyButtons.length).toBeGreaterThan(0);
});

test("example equation is copied to the clipboard when 'copy' icon button is activated", async () => {
// Arrange
const writeTextMock = jest.fn();
const clipboardFnMock = jest.fn();
jest.spyOn(
global.navigator,
"clipboard",
"get",
).mockReturnValue({
// Only interested in the "writeText" function.
writeText: writeTextMock,
// The other functions are here to avoid TS from complaining.
read: clipboardFnMock,
readText: clipboardFnMock,
write: clipboardFnMock,
addEventListener: clipboardFnMock,
dispatchEvent: clipboardFnMock,
removeEventListener: clipboardFnMock,
});

render(
<LockedFunctionSettings
{...defaultProps}
expanded={true}
onChangeProps={onChangeProps}
/>,
{wrapper: RenderStateRoot},
);

// Act - choose a category to get a listing of examples
const categoryDropdown =
screen.getByLabelText("Choose a category");
await userEvent.click(categoryDropdown);
const categoryOption = screen.getAllByRole("option")[0];
await userEvent.click(categoryOption);

// Act - click the copy button for the first example
const copyButton = screen.getAllByLabelText("copy example")[0];
await userEvent.click(copyButton);

// Assert - clipboard receives example text
expect(writeTextMock).toHaveBeenCalledWith("bar");
});

test("example equation is copied to the equation field when 'paste' icon button is activated", async () => {
// Arrange
render(
<LockedFunctionSettings
{...defaultProps}
expanded={true}
onChangeProps={onChangeProps}
/>,
{wrapper: RenderStateRoot},
);

// Act - choose a category to get a listing of examples
const categoryDropdown =
screen.getByLabelText("Choose a category");
await userEvent.click(categoryDropdown);
const categoryOption = screen.getAllByRole("option")[0];
await userEvent.click(categoryOption);

// Act - click the copy button for the first example
const pasteButton =
screen.getAllByLabelText("paste example")[0];
await userEvent.click(pasteButton);

// Assert - clipboard receives example text
expect(onChangeProps).toHaveBeenCalledWith({equation: "bar"});
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const examples: {[k: string]: string[]} = {
linear: ["x + 5", "1/2x - 2"],
polynomial: ["1/2x^2 + 3x - 4", "(1/3)x^3 - 2x^2 + 3x - 4"],
trigonometric: ["sin(x) * 3", "arctan(2x) + 4"],
};

export default examples;
Loading

0 comments on commit bdb382f

Please sign in to comment.