@khanacademy/perseus 1.4.1 → 2.0.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/es/index.css +9 -245
  3. package/dist/es/index.js +138 -584
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/index.css +9 -245
  6. package/dist/index.js +135 -581
  7. package/dist/index.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/__stories__/hints-renderer.stories.jsx +9 -1
  10. package/src/__testdata__/graphie_testdata.js +53 -0
  11. package/src/__tests__/__snapshots__/perseus-markdown_test.jsx.snap +0 -9
  12. package/src/__tests__/__snapshots__/renderer_test.jsx.snap +0 -9
  13. package/src/components/__stories__/graphie.stories.jsx +5 -1
  14. package/src/hint-renderer.jsx +11 -14
  15. package/src/perseus-api.jsx +0 -23
  16. package/src/perseus-markdown.jsx +0 -25
  17. package/src/styles/constants.js +0 -21
  18. package/src/styles/global-constants.js +0 -6
  19. package/src/styles/khan-exercise.css +9 -1
  20. package/src/styles/perseus-renderer.less +0 -259
  21. package/src/styles/variables.less +0 -11
  22. package/src/styles/variables.shared.less +0 -10
  23. package/src/types.js +0 -16
  24. package/src/widgets/__stories__/input-number.stories.jsx +0 -14
  25. package/src/widgets/__stories__/radio.stories.jsx +0 -3
  26. package/src/widgets/__tests__/__snapshots__/definition_test.js.snap +18 -28
  27. package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +0 -9
  28. package/src/widgets/__tests__/__snapshots__/image_test.jsx.snap +0 -18
  29. package/src/widgets/__tests__/__snapshots__/numeric-input_test.jsx.snap +2 -440
  30. package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +66 -3726
  31. package/src/widgets/__tests__/numeric-input_test.jsx +54 -118
  32. package/src/widgets/__tests__/radio/choice-icon_test.jsx +25 -75
  33. package/src/widgets/__tests__/radio_test.jsx +362 -394
  34. package/src/widgets/definition.jsx +27 -14
  35. package/src/widgets/input-number.jsx +2 -60
  36. package/src/widgets/numeric-input.jsx +17 -89
  37. package/src/widgets/radio/__stories__/base-radio.stories.jsx +1 -3
  38. package/src/widgets/radio/__stories__/choice-icon.stories.jsx +23 -105
  39. package/src/widgets/radio/__stories__/option-status.stories.jsx +0 -8
  40. package/src/widgets/radio/base-radio.jsx +13 -96
  41. package/src/widgets/radio/choice-icon/choice-icon.jsx +177 -50
  42. package/src/widgets/radio/choice-icon/cross-out-line.jsx +9 -13
  43. package/src/widgets/radio/choice-icon/shared-styles.js +2 -3
  44. package/src/widgets/radio/choice.jsx +5 -59
  45. package/src/widgets/radio/option-status.jsx +2 -14
  46. package/src/components/__stories__/possible-answers.stories.jsx +0 -22
  47. package/src/components/possible-answers.jsx +0 -36
  48. package/src/styles/widgets/definition.less +0 -86
  49. package/src/widgets/radio/__stories__/sat-styles.stories.jsx +0 -191
  50. package/src/widgets/radio/choice-icon/library-choice-icon.jsx +0 -225
  51. package/src/widgets/radio/choice-icon/sat-choice-icon.jsx +0 -120
@@ -1,13 +1,10 @@
1
1
  // @flow
2
2
 
3
- import {RenderStateRoot} from "@khanacademy/wonder-blocks-core";
4
- import {render, screen} from "@testing-library/react";
3
+ import {screen} from "@testing-library/react";
5
4
  import userEvent from "@testing-library/user-event";
6
- import * as React from "react";
7
5
 
8
6
  import {testDependencies} from "../../../../../testing/test-dependencies.js";
9
7
  import * as Dependencies from "../../dependencies.js";
10
- import * as Perseus from "../../index.js";
11
8
  import {
12
9
  question1AndAnswer,
13
10
  multipleAnswers,
@@ -23,95 +20,53 @@ import {
23
20
  unionAnswerForms,
24
21
  } from "../numeric-input.jsx";
25
22
 
26
- import type {APIOptions} from "../../types.js";
23
+ import {renderQuestion} from "./renderQuestion.jsx";
24
+
27
25
  import type {Rubric} from "../numeric-input.jsx";
28
26
 
29
- const renderQuestion = (question, apiOptions: APIOptions) => {
30
- // Perseus.init() registers all of the widgets synchronously so
31
- // it's okay to ignore the promise it returns.
32
- Perseus.init({skipMathJax: true});
33
-
34
- let renderer = null;
35
- const {container} = render(
36
- <RenderStateRoot>
37
- <Perseus.Renderer
38
- ref={(node) => (renderer = node)}
39
- content={question.content}
40
- images={question.images}
41
- widgets={question.widgets}
42
- problemNum={0}
43
- apiOptions={apiOptions}
44
- />
45
- </RenderStateRoot>,
46
- );
47
- if (!renderer) {
48
- throw new Error(`Not rendered`);
49
- }
50
- return {container, renderer};
51
- };
52
-
53
- const options: $ReadOnlyArray<APIOptions> = [
54
- {satStyling: true},
55
- {satStyling: false},
56
- ];
57
-
58
- describe.each(options)(
59
- "numeric-input widget (options: %j)",
60
- (apiOptions: APIOptions) => {
61
- describe("question", () => {
62
- const [question, correct, incorrect] = question1AndAnswer;
63
-
64
- beforeEach(() => {
65
- jest.spyOn(Dependencies, "getDependencies").mockReturnValue(
66
- testDependencies,
67
- );
68
- });
69
-
70
- it("Should accept the right answer", () => {
71
- // Arrange
72
- const {renderer} = renderQuestion(question, apiOptions);
73
-
74
- // Act
75
- userEvent.paste(
76
- screen.getByRole("textbox", {hidden: true}),
77
- correct,
78
- );
79
-
80
- // Assert
81
- expect(renderer).toHaveBeenAnsweredCorrectly();
82
- });
83
-
84
- it("Should render predictably", () => {
85
- // Arrange
86
- const {container} = renderQuestion(question, apiOptions);
87
- expect(container).toMatchSnapshot("first render");
88
-
89
- // Act
90
- userEvent.paste(
91
- screen.getByRole("textbox", {hidden: true}),
92
- correct,
93
- );
94
-
95
- // Assert
96
- expect(container).toMatchSnapshot("after interaction");
97
- });
98
-
99
- it("should reject an incorrect answer", () => {
100
- // Arrange
101
- const {renderer} = renderQuestion(question, apiOptions);
102
-
103
- // Act
104
- userEvent.paste(
105
- screen.getByRole("textbox", {hidden: true}),
106
- incorrect,
107
- );
108
-
109
- // Assert
110
- expect(renderer).toHaveBeenAnsweredIncorrectly();
111
- });
112
- });
113
- },
114
- );
27
+ describe("numeric-input widget", () => {
28
+ const [question, correct, incorrect] = question1AndAnswer;
29
+
30
+ beforeEach(() => {
31
+ jest.spyOn(Dependencies, "getDependencies").mockReturnValue(
32
+ testDependencies,
33
+ );
34
+ });
35
+
36
+ it("Should accept the right answer", () => {
37
+ // Arrange
38
+ const {renderer} = renderQuestion(question);
39
+
40
+ // Act
41
+ userEvent.paste(screen.getByRole("textbox", {hidden: true}), correct);
42
+
43
+ // Assert
44
+ expect(renderer).toHaveBeenAnsweredCorrectly();
45
+ });
46
+
47
+ it("Should render predictably", () => {
48
+ // Arrange
49
+ const {container} = renderQuestion(question);
50
+ expect(container).toMatchSnapshot("first render");
51
+
52
+ // Act
53
+ userEvent.paste(screen.getByRole("textbox", {hidden: true}), correct);
54
+
55
+ // Assert
56
+ expect(container).toMatchSnapshot("after interaction");
57
+ });
58
+
59
+ it("should reject an incorrect answer", () => {
60
+ // Arrange
61
+ const {renderer} = renderQuestion(question);
62
+
63
+ // Act
64
+ userEvent.paste(screen.getByRole("textbox", {hidden: true}), incorrect);
65
+
66
+ // Assert
67
+ expect(renderer).toHaveBeenAnsweredIncorrectly();
68
+ });
69
+ });
115
70
 
116
71
  describe("static function getOneCorrectAnswerFromRubric", () => {
117
72
  beforeEach(() => {
@@ -371,9 +326,7 @@ describe("Numeric input widget", () => {
371
326
 
372
327
  it("can handle multiple correct answers (Part one)", () => {
373
328
  // Arrange
374
- const {renderer} = renderQuestion(multipleAnswers, {
375
- satStyling: false,
376
- });
329
+ const {renderer} = renderQuestion(multipleAnswers);
377
330
 
378
331
  // Act
379
332
  userEvent.paste(screen.getByRole("textbox", {hidden: true}), "1");
@@ -384,9 +337,7 @@ describe("Numeric input widget", () => {
384
337
 
385
338
  it("can handle multiple correct answers (Part two)", () => {
386
339
  // Arrange
387
- const {renderer} = renderQuestion(multipleAnswers, {
388
- satStyling: false,
389
- });
340
+ const {renderer} = renderQuestion(multipleAnswers);
390
341
 
391
342
  // Act
392
343
  userEvent.paste(screen.getByRole("textbox", {hidden: true}), "2");
@@ -397,9 +348,7 @@ describe("Numeric input widget", () => {
397
348
 
398
349
  it("can handle duplicated answers", () => {
399
350
  // Arrange
400
- const {renderer} = renderQuestion(duplicatedAnswers, {
401
- satStyling: false,
402
- });
351
+ const {renderer} = renderQuestion(duplicatedAnswers);
403
352
 
404
353
  // Act
405
354
  userEvent.paste(screen.getByRole("textbox", {hidden: true}), "2.4");
@@ -410,9 +359,7 @@ describe("Numeric input widget", () => {
410
359
 
411
360
  it("can handle coefficients", () => {
412
361
  // Arrange
413
- const {renderer} = renderQuestion(withCoefficient, {
414
- satStyling: false,
415
- });
362
+ const {renderer} = renderQuestion(withCoefficient);
416
363
 
417
364
  // Act
418
365
  userEvent.paste(screen.getByRole("textbox", {hidden: true}), "1.0");
@@ -423,9 +370,7 @@ describe("Numeric input widget", () => {
423
370
 
424
371
  it("handles answers that are percentages", () => {
425
372
  // Arrange
426
- const {renderer} = renderQuestion(percentageProblem, {
427
- satStyling: false,
428
- });
373
+ const {renderer} = renderQuestion(percentageProblem);
429
374
 
430
375
  // Act
431
376
  userEvent.paste(screen.getByRole("textbox", {hidden: true}), "33%");
@@ -436,9 +381,7 @@ describe("Numeric input widget", () => {
436
381
 
437
382
  it("handles answers that are decimals", () => {
438
383
  // Arrange
439
- const {renderer} = renderQuestion(multipleAnswersWithDecimals, {
440
- satStyling: false,
441
- });
384
+ const {renderer} = renderQuestion(multipleAnswersWithDecimals);
442
385
 
443
386
  // Act
444
387
  userEvent.paste(screen.getByRole("textbox", {hidden: true}), "12.2");
@@ -450,7 +393,6 @@ describe("Numeric input widget", () => {
450
393
  it("supports being static", () => {
451
394
  // Arrange
452
395
  renderQuestion(multipleAnswersWithDecimals, {
453
- satStyling: false,
454
396
  staticRender: true,
455
397
  });
456
398
 
@@ -461,7 +403,6 @@ describe("Numeric input widget", () => {
461
403
 
462
404
  it("styles differently on mobile", () => {
463
405
  const {container} = renderQuestion(multipleAnswersWithDecimals, {
464
- satStyling: false,
465
406
  // I wish this was more clear but this is how mobile
466
407
  // rendering is triggered
467
408
  customKeypad: true,
@@ -471,9 +412,7 @@ describe("Numeric input widget", () => {
471
412
  });
472
413
 
473
414
  it("can be focused", () => {
474
- const {renderer} = renderQuestion(question1, {
475
- satStyling: true,
476
- });
415
+ const {renderer} = renderQuestion(question1);
477
416
 
478
417
  // Act
479
418
  const gotFocus = renderer.focus();
@@ -490,7 +429,6 @@ describe("Numeric input widget", () => {
490
429
  const testCallback = jest.fn();
491
430
 
492
431
  const {renderer} = renderQuestion(question1, {
493
- satStyling: true,
494
432
  interactionCallback: testCallback,
495
433
  });
496
434
 
@@ -502,9 +440,7 @@ describe("Numeric input widget", () => {
502
440
  });
503
441
 
504
442
  it("can be blurred", () => {
505
- const {renderer} = renderQuestion(question1, {
506
- satStyling: true,
507
- });
443
+ const {renderer} = renderQuestion(question1);
508
444
 
509
445
  // Act
510
446
  const input = screen.getByRole("textbox", {hidden: true});
@@ -6,10 +6,7 @@ import * as React from "react";
6
6
  import "@testing-library/jest-dom"; // Imports custom mathers
7
7
 
8
8
  import ChoiceIcon from "../../radio/choice-icon/choice-icon.jsx";
9
- import {
10
- SAT_ICON_SIZE,
11
- LIBRARY_ICON_SIZE,
12
- } from "../../radio/choice-icon/shared-styles.js";
9
+ import {CHOICE_ICON_SIZE} from "../../radio/choice-icon/shared-styles.js";
13
10
 
14
11
  function renderChoiceIcon(options) {
15
12
  const defaultOptions = {
@@ -21,11 +18,9 @@ function renderChoiceIcon(options) {
21
18
  pressed: false,
22
19
  correct: false,
23
20
  showCorrectness: false,
24
- product: "library",
25
21
  multipleSelect: false,
26
22
  reviewMode: false,
27
23
  previouslyAnswered: false,
28
- transparentBackground: false,
29
24
  };
30
25
 
31
26
  const overwrittenOptions = {...defaultOptions, ...options};
@@ -34,41 +29,33 @@ function renderChoiceIcon(options) {
34
29
  }
35
30
 
36
31
  describe("choice icon", () => {
37
- describe.each([["sat"], ["library"]])("product: %s", (product) => {
38
- describe.each([[true], [false]])(
39
- "multipleSelect: %s",
40
- (multipleSelect) => {
41
- it("renders with the correct border radius", () => {
42
- // Arrange
43
- let expectedRadius;
44
- if (multipleSelect) {
45
- expectedRadius = 3;
46
- } else if (product === "sat") {
47
- expectedRadius = SAT_ICON_SIZE;
48
- } else {
49
- expectedRadius = LIBRARY_ICON_SIZE;
50
- }
51
-
52
- // Act
53
- renderChoiceIcon({product, multipleSelect});
54
-
55
- const choiceWrapper = screen.getByTestId(
56
- `choice-icon__${product}-choice-icon`,
57
- );
58
-
59
- // Assert
60
- expect(choiceWrapper).toHaveStyle(
61
- `border-radius: ${expectedRadius}px`,
62
- );
63
- });
64
- },
65
- );
32
+ describe.each([[true], [false]])("multipleSelect: %s", (multipleSelect) => {
33
+ it("renders with the correct border radius", () => {
34
+ // Arrange
35
+ let expectedRadius;
36
+ if (multipleSelect) {
37
+ expectedRadius = 3;
38
+ } else {
39
+ expectedRadius = CHOICE_ICON_SIZE;
40
+ }
41
+
42
+ // Act
43
+ renderChoiceIcon({multipleSelect});
44
+
45
+ const choiceWrapper = screen.getByTestId(
46
+ `choice-icon__library-choice-icon`,
47
+ );
48
+
49
+ // Assert
50
+ expect(choiceWrapper).toHaveStyle(
51
+ `border-radius: ${expectedRadius}px`,
52
+ );
53
+ });
66
54
  });
67
55
 
68
- it("shows a checkmark for correct answers when product is set to library", () => {
56
+ it("shows a checkmark for correct answers", () => {
69
57
  // Arrange & Act
70
58
  renderChoiceIcon({
71
- product: "library",
72
59
  correct: true,
73
60
  reviewMode: true,
74
61
  checked: true,
@@ -84,10 +71,9 @@ describe("choice icon", () => {
84
71
  expect(choiceAttrib).toContain("circleCorrect");
85
72
  });
86
73
 
87
- it("shows a dash for incorrect answers when product is set to library", () => {
74
+ it("shows a dash for incorrect answers", () => {
88
75
  // Arrange & Act
89
76
  renderChoiceIcon({
90
- product: "library",
91
77
  correct: false,
92
78
  reviewMode: true,
93
79
  checked: true,
@@ -102,40 +88,4 @@ describe("choice icon", () => {
102
88
  // Assert
103
89
  expect(choiceAttrib).toContain("circleIncorrect");
104
90
  });
105
-
106
- it("displays green border for correct answers when product is set to sat", () => {
107
- // Arrange & Act
108
- renderChoiceIcon({
109
- product: "sat",
110
- correct: true,
111
- reviewMode: true,
112
- checked: true,
113
- });
114
-
115
- const choiceWrapper = screen.getByTestId(
116
- `choice-icon__sat-choice-icon`,
117
- );
118
- const choiceStyle = choiceWrapper.getAttribute("style");
119
-
120
- // Assert
121
- expect(choiceStyle).toContain("border-color: #009900");
122
- });
123
-
124
- it("displays red border for incorrect answers when product is set to sat", () => {
125
- // Arrange & Act
126
- renderChoiceIcon({
127
- product: "sat",
128
- correct: false,
129
- reviewMode: true,
130
- checked: true,
131
- });
132
-
133
- const choiceWrapper = screen.getByTestId(
134
- `choice-icon__sat-choice-icon`,
135
- );
136
- const choiceStyle = choiceWrapper.getAttribute("style");
137
-
138
- // Assert
139
- expect(choiceStyle).toContain("border-color: #990000");
140
- });
141
91
  });