@khanacademy/perseus 1.4.0 → 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.
- package/CHANGELOG.md +22 -0
- package/dist/es/index.css +9 -245
- package/dist/es/index.js +138 -584
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +9 -245
- package/dist/index.js +135 -581
- package/dist/index.js.map +1 -1
- package/package.json +36 -36
- package/src/__stories__/hints-renderer.stories.jsx +9 -1
- package/src/__testdata__/graphie_testdata.js +53 -0
- package/src/__tests__/__snapshots__/perseus-markdown_test.jsx.snap +0 -9
- package/src/__tests__/__snapshots__/renderer_test.jsx.snap +0 -9
- package/src/components/__stories__/graphie.stories.jsx +5 -1
- package/src/hint-renderer.jsx +11 -14
- package/src/perseus-api.jsx +0 -23
- package/src/perseus-markdown.jsx +0 -25
- package/src/styles/constants.js +0 -21
- package/src/styles/global-constants.js +0 -6
- package/src/styles/khan-exercise.css +9 -1
- package/src/styles/perseus-renderer.less +0 -259
- package/src/styles/variables.less +0 -11
- package/src/styles/variables.shared.less +0 -10
- package/src/types.js +0 -16
- package/src/widgets/__stories__/input-number.stories.jsx +0 -14
- package/src/widgets/__stories__/radio.stories.jsx +0 -3
- package/src/widgets/__tests__/__snapshots__/definition_test.js.snap +18 -28
- package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +0 -9
- package/src/widgets/__tests__/__snapshots__/image_test.jsx.snap +0 -18
- package/src/widgets/__tests__/__snapshots__/numeric-input_test.jsx.snap +2 -440
- package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +66 -3726
- package/src/widgets/__tests__/numeric-input_test.jsx +54 -118
- package/src/widgets/__tests__/radio/choice-icon_test.jsx +25 -75
- package/src/widgets/__tests__/radio_test.jsx +362 -394
- package/src/widgets/definition.jsx +27 -14
- package/src/widgets/input-number.jsx +2 -60
- package/src/widgets/numeric-input.jsx +17 -89
- package/src/widgets/radio/__stories__/base-radio.stories.jsx +1 -3
- package/src/widgets/radio/__stories__/choice-icon.stories.jsx +23 -105
- package/src/widgets/radio/__stories__/option-status.stories.jsx +0 -8
- package/src/widgets/radio/base-radio.jsx +13 -96
- package/src/widgets/radio/choice-icon/choice-icon.jsx +177 -50
- package/src/widgets/radio/choice-icon/cross-out-line.jsx +9 -13
- package/src/widgets/radio/choice-icon/shared-styles.js +2 -3
- package/src/widgets/radio/choice.jsx +5 -59
- package/src/widgets/radio/option-status.jsx +2 -14
- package/src/components/__stories__/possible-answers.stories.jsx +0 -22
- package/src/components/possible-answers.jsx +0 -36
- package/src/styles/widgets/definition.less +0 -86
- package/src/widgets/radio/__stories__/sat-styles.stories.jsx +0 -191
- package/src/widgets/radio/choice-icon/library-choice-icon.jsx +0 -225
- package/src/widgets/radio/choice-icon/sat-choice-icon.jsx +0 -120
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
// @flow
|
|
2
2
|
|
|
3
|
-
import {
|
|
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
|
|
23
|
+
import {renderQuestion} from "./renderQuestion.jsx";
|
|
24
|
+
|
|
27
25
|
import type {Rubric} from "../numeric-input.jsx";
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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([[
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
|
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
|
|
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
|
});
|