@khanacademy/perseus-editor 2.12.1 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,17 +1,17 @@
1
- import PropTypes from "prop-types";
2
1
  import * as React from "react";
3
- type Props = any;
2
+ type ItemExtrasProps = {
3
+ calculator: boolean;
4
+ chi2Table: boolean;
5
+ periodicTable: boolean;
6
+ periodicTableWithKey: boolean;
7
+ tTable: boolean;
8
+ zTable: boolean;
9
+ };
10
+ type Props = ItemExtrasProps & {
11
+ onChange: (props: Partial<ItemExtrasProps>) => void;
12
+ };
4
13
  declare class ItemExtrasEditor extends React.Component<Props> {
5
- static propTypes: {
6
- calculator: PropTypes.Requireable<boolean>;
7
- chi2Table: PropTypes.Requireable<boolean>;
8
- onChange: PropTypes.Validator<(...args: any[]) => any>;
9
- periodicTable: PropTypes.Requireable<boolean>;
10
- periodicTableWithKey: PropTypes.Requireable<boolean>;
11
- tTable: PropTypes.Requireable<boolean>;
12
- zTable: PropTypes.Requireable<boolean>;
13
- };
14
- static defaultProps: Props;
14
+ static defaultProps: ItemExtrasProps;
15
15
  serialize: () => any;
16
16
  render(): React.ReactNode;
17
17
  }
@@ -11,13 +11,9 @@ type DefaultProps = {
11
11
  buttonSets: Props["buttonSets"];
12
12
  functions: Props["functions"];
13
13
  };
14
- type State = {
15
- isTex: boolean;
16
- };
17
- declare class ExpressionEditor extends React.Component<Props, State> {
14
+ declare class ExpressionEditor extends React.Component<Props> {
18
15
  static widgetName: "expression";
19
16
  static defaultProps: DefaultProps;
20
- constructor(props: Props);
21
17
  change(...args: any[]): any;
22
18
  render(): React.ReactNode;
23
19
  serialize: () => any;
@@ -2,13 +2,16 @@
2
2
  * Controlled component to display and change widget options.
3
3
  */
4
4
  import * as React from "react";
5
+ export type PreferredPopoverDirection = "NONE" | "UP" | "DOWN" | "LEFT" | "RIGHT";
5
6
  type Props = {
6
7
  multipleAnswers: boolean;
7
8
  hideChoicesFromInstructions: boolean;
9
+ preferredPopoverDirection: PreferredPopoverDirection;
8
10
  onChange: (options: {
9
11
  multipleAnswers?: boolean;
10
12
  hideChoicesFromInstructions?: boolean;
13
+ preferredPopoverDirection?: PreferredPopoverDirection;
11
14
  }) => void;
12
15
  };
13
- declare const Behavior: ({ multipleAnswers, hideChoicesFromInstructions, onChange, }: Props) => React.ReactElement;
16
+ declare const Behavior: ({ multipleAnswers, hideChoicesFromInstructions, preferredPopoverDirection, onChange, }: Props) => React.ReactElement;
14
17
  export default Behavior;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Perseus editors",
4
4
  "author": "Khan Academy",
5
5
  "license": "MIT",
6
- "version": "2.12.1",
6
+ "version": "2.14.0",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -22,18 +22,18 @@
22
22
  "test": "bash -c 'yarn --silent --cwd \"../..\" test ${@:0} $($([[ ${@: -1} = -* ]] || [[ ${@: -1} = bash ]]) && echo $PWD)'"
23
23
  },
24
24
  "dependencies": {
25
- "@khanacademy/kas": "^0.3.6",
26
- "@khanacademy/kmath": "^0.1.7",
27
- "@khanacademy/perseus": "^16.0.0",
28
- "@khanacademy/perseus-core": "1.4.0"
25
+ "@khanacademy/kas": "^0.3.7",
26
+ "@khanacademy/kmath": "^0.1.8",
27
+ "@khanacademy/perseus": "^17.1.0",
28
+ "@khanacademy/perseus-core": "1.4.1"
29
29
  },
30
30
  "devDependencies": {
31
- "@khanacademy/wonder-blocks-button": "^6.2.2",
31
+ "@khanacademy/wonder-blocks-button": "^6.2.4",
32
32
  "@khanacademy/wonder-blocks-clickable": "^4.0.11",
33
33
  "@khanacademy/wonder-blocks-color": "^3.0.0",
34
34
  "@khanacademy/wonder-blocks-core": "^6.3.0",
35
- "@khanacademy/wonder-blocks-icon": "2.2.1",
36
35
  "@khanacademy/wonder-blocks-i18n": "^2.0.2",
36
+ "@khanacademy/wonder-blocks-icon": "4.0.0",
37
37
  "@khanacademy/wonder-blocks-spacing": "^4.0.1",
38
38
  "@khanacademy/wonder-blocks-typography": "^2.1.9",
39
39
  "@khanacademy/wonder-stuff-core": "^1.5.1",
@@ -50,12 +50,12 @@
50
50
  "underscore": "^1.4.4"
51
51
  },
52
52
  "peerDependencies": {
53
- "@khanacademy/wonder-blocks-button": "^6.2.2",
53
+ "@khanacademy/wonder-blocks-button": "^6.2.4",
54
54
  "@khanacademy/wonder-blocks-clickable": "^4.0.11",
55
55
  "@khanacademy/wonder-blocks-color": "^3.0.0",
56
56
  "@khanacademy/wonder-blocks-core": "^6.3.0",
57
- "@khanacademy/wonder-blocks-icon": "2.2.1",
58
57
  "@khanacademy/wonder-blocks-i18n": "^2.0.2",
58
+ "@khanacademy/wonder-blocks-icon": "4.0.0",
59
59
  "@khanacademy/wonder-blocks-spacing": "^4.0.1",
60
60
  "@khanacademy/wonder-blocks-typography": "^2.1.9",
61
61
  "@khanacademy/wonder-stuff-core": "^1.5.1",
@@ -0,0 +1,68 @@
1
+ import "@testing-library/jest-dom";
2
+ import {render, screen} from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import React from "react";
5
+
6
+ import ItemExtrasEditor from "../item-extras-editor";
7
+
8
+ describe("ItemExtrasEditor", () => {
9
+ it("should render correctly with default props", () => {
10
+ // Act
11
+ render(<ItemExtrasEditor onChange={() => {}} />);
12
+
13
+ // Assert
14
+ expect(screen.getByLabelText("Show calculator:")).not.toBeChecked();
15
+ expect(screen.getByLabelText("Show periodic table:")).not.toBeChecked();
16
+ expect(
17
+ screen.queryByText("Include key/legend with periodic table:"),
18
+ ).not.toBeInTheDocument();
19
+ expect(
20
+ screen.getByLabelText("Show z table (statistics):"),
21
+ ).not.toBeChecked();
22
+ expect(
23
+ screen.getByLabelText("Show t table (statistics):"),
24
+ ).not.toBeChecked();
25
+ expect(
26
+ screen.getByLabelText("Show chi-squared table (statistics):"),
27
+ ).not.toBeChecked();
28
+ });
29
+
30
+ it("should call onChange with updated calculator value", () => {
31
+ // Arrange
32
+ const onChangeMock = jest.fn();
33
+ render(<ItemExtrasEditor calculator={false} onChange={onChangeMock} />);
34
+ const checkbox = screen.getByLabelText("Show calculator:");
35
+
36
+ // Act
37
+ userEvent.click(checkbox);
38
+
39
+ // Assert
40
+ expect(onChangeMock).toHaveBeenCalledWith({calculator: true});
41
+ });
42
+
43
+ it("should call onChange with updated periodicTableWithKey value when periodicTable is unchecked", () => {
44
+ // Arrange
45
+ const onChangeMock = jest.fn();
46
+ render(
47
+ <ItemExtrasEditor
48
+ periodicTable={true}
49
+ periodicTableWithKey={true}
50
+ onChange={onChangeMock}
51
+ />,
52
+ );
53
+ const checkbox = screen.getByLabelText("Show periodic table:");
54
+
55
+ // Act
56
+ userEvent.click(checkbox);
57
+
58
+ // Assert
59
+ // visible when periodicTable is checked
60
+ expect(
61
+ screen.getByText("Include key/legend with periodic table:"),
62
+ ).toBeInTheDocument();
63
+ expect(onChangeMock).toHaveBeenCalledWith({
64
+ periodicTable: false,
65
+ periodicTableWithKey: false,
66
+ });
67
+ });
68
+ });
@@ -1,23 +1,23 @@
1
1
  import {components} from "@khanacademy/perseus";
2
- import PropTypes from "prop-types";
3
2
  import * as React from "react";
4
3
 
5
4
  const {InfoTip} = components;
6
5
 
7
- type Props = any;
6
+ type ItemExtrasProps = {
7
+ calculator: boolean;
8
+ chi2Table: boolean;
9
+ periodicTable: boolean;
10
+ periodicTableWithKey: boolean;
11
+ tTable: boolean;
12
+ zTable: boolean;
13
+ };
8
14
 
9
- class ItemExtrasEditor extends React.Component<Props> {
10
- static propTypes = {
11
- calculator: PropTypes.bool,
12
- chi2Table: PropTypes.bool,
13
- onChange: PropTypes.func.isRequired,
14
- periodicTable: PropTypes.bool,
15
- periodicTableWithKey: PropTypes.bool,
16
- tTable: PropTypes.bool,
17
- zTable: PropTypes.bool,
18
- };
15
+ type Props = ItemExtrasProps & {
16
+ onChange: (props: Partial<ItemExtrasProps>) => void;
17
+ };
19
18
 
20
- static defaultProps: Props = {
19
+ class ItemExtrasEditor extends React.Component<Props> {
20
+ static defaultProps: ItemExtrasProps = {
21
21
  calculator: false,
22
22
  chi2Table: false,
23
23
  periodicTable: false,
@@ -71,6 +71,10 @@ class ItemExtrasEditor extends React.Component<Props> {
71
71
  onChange={(e) => {
72
72
  this.props.onChange({
73
73
  periodicTable: e.target.checked,
74
+ // If the periodic table is unchecked,
75
+ // this needs to be reset. If table is
76
+ // checked, it should already be false.
77
+ periodicTableWithKey: false,
74
78
  });
75
79
  }}
76
80
  />
@@ -1,5 +1,6 @@
1
1
  import {Dependencies} from "@khanacademy/perseus";
2
2
  import {render, screen} from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
3
4
  import * as React from "react";
4
5
 
5
6
  import "@testing-library/jest-dom";
@@ -53,4 +54,350 @@ describe("expression-editor", () => {
53
54
 
54
55
  expect(await screen.findByText(/π/)).toBeInTheDocument();
55
56
  });
57
+
58
+ it("should toggle multiplication checkbox", () => {
59
+ const onChangeMock = jest.fn();
60
+
61
+ render(<ExpressionEditor onChange={onChangeMock} />);
62
+
63
+ userEvent.click(
64
+ screen.getByRole("checkbox", {
65
+ name: "Use × for rendering multiplication instead of a center dot.",
66
+ }),
67
+ );
68
+
69
+ expect(onChangeMock).toBeCalledWith({times: true});
70
+ });
71
+
72
+ it("should be possible to change function variables", () => {
73
+ const onChangeMock = jest.fn();
74
+
75
+ render(<ExpressionEditor onChange={onChangeMock} functions={[]} />);
76
+
77
+ const input = screen.getByRole("textbox", {
78
+ name: "Function variables:",
79
+ });
80
+
81
+ userEvent.type(input, "x");
82
+
83
+ expect(onChangeMock).toBeCalledWith({functions: ["x"]});
84
+ });
85
+
86
+ it("should toggle division checkbox", () => {
87
+ const onChangeMock = jest.fn();
88
+
89
+ render(<ExpressionEditor onChange={onChangeMock} />);
90
+
91
+ userEvent.click(
92
+ screen.getByRole("checkbox", {
93
+ name: "show \\div button",
94
+ }),
95
+ );
96
+
97
+ expect(onChangeMock).toBeCalledWith(
98
+ {
99
+ answerForms: [],
100
+ buttonSets: ["basic+div"],
101
+ functions: ["f", "g", "h"],
102
+ times: false,
103
+ },
104
+ undefined,
105
+ );
106
+ });
107
+
108
+ it("should toggle trig checkbox", () => {
109
+ const onChangeMock = jest.fn();
110
+
111
+ render(<ExpressionEditor onChange={onChangeMock} />);
112
+
113
+ userEvent.click(
114
+ screen.getByRole("checkbox", {
115
+ name: "trig",
116
+ }),
117
+ );
118
+
119
+ expect(onChangeMock).toBeCalledWith({
120
+ buttonSets: ["basic", "trig"],
121
+ });
122
+ });
123
+
124
+ it("should toggle prealgebra checkbox", () => {
125
+ const onChangeMock = jest.fn();
126
+
127
+ render(<ExpressionEditor onChange={onChangeMock} />);
128
+
129
+ userEvent.click(
130
+ screen.getByRole("checkbox", {
131
+ name: "prealgebra",
132
+ }),
133
+ );
134
+
135
+ expect(onChangeMock).toBeCalledWith({
136
+ buttonSets: ["basic", "prealgebra"],
137
+ });
138
+ });
139
+
140
+ it("should toggle prealgebra checkbox", () => {
141
+ const onChangeMock = jest.fn();
142
+
143
+ render(<ExpressionEditor onChange={onChangeMock} />);
144
+
145
+ userEvent.click(
146
+ screen.getByRole("checkbox", {
147
+ name: "logarithms",
148
+ }),
149
+ );
150
+
151
+ expect(onChangeMock).toBeCalledWith({
152
+ buttonSets: ["basic", "logarithms"],
153
+ });
154
+ });
155
+
156
+ it("should toggle prealgebra checkbox", () => {
157
+ const onChangeMock = jest.fn();
158
+
159
+ render(<ExpressionEditor onChange={onChangeMock} />);
160
+
161
+ userEvent.click(
162
+ screen.getByRole("checkbox", {
163
+ name: "basic relations",
164
+ }),
165
+ );
166
+
167
+ expect(onChangeMock).toBeCalledWith({
168
+ buttonSets: ["basic", "basic relations"],
169
+ });
170
+ });
171
+
172
+ it("should toggle prealgebra checkbox", () => {
173
+ const onChangeMock = jest.fn();
174
+
175
+ render(<ExpressionEditor onChange={onChangeMock} />);
176
+
177
+ userEvent.click(
178
+ screen.getByRole("checkbox", {
179
+ name: "advanced relations",
180
+ }),
181
+ );
182
+
183
+ expect(onChangeMock).toBeCalledWith({
184
+ buttonSets: ["basic", "advanced relations"],
185
+ });
186
+ });
187
+
188
+ it("should be possible to add an answer", () => {
189
+ const onChangeMock = jest.fn();
190
+
191
+ render(<ExpressionEditor onChange={onChangeMock} />);
192
+
193
+ userEvent.click(
194
+ screen.getByRole("button", {
195
+ name: "Add new answer",
196
+ }),
197
+ );
198
+
199
+ expect(onChangeMock).toBeCalledWith(
200
+ {
201
+ answerForms: [
202
+ {
203
+ considered: "correct",
204
+ form: false,
205
+ key: 0,
206
+ simplify: false,
207
+ value: "",
208
+ },
209
+ ],
210
+ buttonSets: ["basic"],
211
+ functions: ["f", "g", "h"],
212
+ times: false,
213
+ },
214
+ undefined,
215
+ );
216
+ });
217
+
218
+ it("should be possible to update answer", () => {
219
+ const onChangeMock = jest.fn();
220
+
221
+ render(
222
+ <ExpressionEditor
223
+ onChange={onChangeMock}
224
+ answerForms={[
225
+ {
226
+ considered: "correct",
227
+ form: false,
228
+ key: "0",
229
+ simplify: false,
230
+ value: "",
231
+ },
232
+ ]}
233
+ />,
234
+ );
235
+
236
+ userEvent.click(
237
+ screen.getByRole("switch", {
238
+ name: "open math keypad",
239
+ }),
240
+ );
241
+
242
+ userEvent.click(
243
+ screen.getByRole("button", {
244
+ name: "9",
245
+ }),
246
+ );
247
+
248
+ expect(onChangeMock).toBeCalledWith(
249
+ {
250
+ answerForms: [
251
+ {
252
+ buttonSets: ["basic"],
253
+ buttonsVisible: "focused",
254
+ considered: "correct",
255
+ form: false,
256
+ functions: ["f", "g", "h"],
257
+ key: "0",
258
+ linterContext: {
259
+ contentType: "",
260
+ highlightLint: false,
261
+ paths: [],
262
+ stack: [],
263
+ },
264
+ simplify: false,
265
+ times: false,
266
+ value: "9",
267
+ },
268
+ ],
269
+ buttonSets: ["basic"],
270
+ functions: ["f", "g", "h"],
271
+ times: false,
272
+ },
273
+ undefined,
274
+ );
275
+ });
276
+
277
+ it("should be possible to toggle same form", () => {
278
+ const onChangeMock = jest.fn();
279
+
280
+ render(
281
+ <ExpressionEditor
282
+ onChange={onChangeMock}
283
+ answerForms={[
284
+ {
285
+ considered: "correct",
286
+ form: false,
287
+ key: "0",
288
+ simplify: false,
289
+ value: "",
290
+ },
291
+ ]}
292
+ />,
293
+ );
294
+
295
+ userEvent.click(
296
+ screen.getByRole("checkbox", {
297
+ name: "Answer expression must have the same form.",
298
+ }),
299
+ );
300
+
301
+ expect(onChangeMock).toBeCalledWith(
302
+ {
303
+ answerForms: [
304
+ {
305
+ considered: "correct",
306
+ form: true,
307
+ key: "0",
308
+ simplify: false,
309
+ value: "",
310
+ },
311
+ ],
312
+ buttonSets: ["basic"],
313
+ functions: ["f", "g", "h"],
314
+ times: false,
315
+ },
316
+ undefined,
317
+ );
318
+ });
319
+
320
+ it("should be possible to toggle expanded and simplified", () => {
321
+ const onChangeMock = jest.fn();
322
+
323
+ render(
324
+ <ExpressionEditor
325
+ onChange={onChangeMock}
326
+ answerForms={[
327
+ {
328
+ considered: "correct",
329
+ form: false,
330
+ key: "0",
331
+ simplify: false,
332
+ value: "",
333
+ },
334
+ ]}
335
+ />,
336
+ );
337
+
338
+ userEvent.click(
339
+ screen.getByRole("checkbox", {
340
+ name: "Answer expression must be fully expanded and simplified.",
341
+ }),
342
+ );
343
+
344
+ expect(onChangeMock).toBeCalledWith(
345
+ {
346
+ answerForms: [
347
+ {
348
+ considered: "correct",
349
+ form: false,
350
+ key: "0",
351
+ simplify: true,
352
+ value: "",
353
+ },
354
+ ],
355
+ buttonSets: ["basic"],
356
+ functions: ["f", "g", "h"],
357
+ times: false,
358
+ },
359
+ undefined,
360
+ );
361
+ });
362
+
363
+ it("should be possible to delete answer", () => {
364
+ const onChangeMock = jest.fn();
365
+
366
+ render(
367
+ <ExpressionEditor
368
+ onChange={onChangeMock}
369
+ answerForms={[
370
+ {
371
+ considered: "correct",
372
+ form: false,
373
+ key: "0",
374
+ simplify: false,
375
+ value: "",
376
+ },
377
+ ]}
378
+ />,
379
+ );
380
+
381
+ userEvent.click(
382
+ screen.getByRole("button", {
383
+ name: "Delete",
384
+ }),
385
+ );
386
+
387
+ userEvent.click(
388
+ screen.getByRole("button", {
389
+ name: "I'm sure!",
390
+ }),
391
+ );
392
+
393
+ expect(onChangeMock).toBeCalledWith(
394
+ {
395
+ answerForms: [],
396
+ buttonSets: ["basic"],
397
+ functions: ["f", "g", "h"],
398
+ times: false,
399
+ },
400
+ undefined,
401
+ );
402
+ });
56
403
  });