@khanacademy/perseus 1.0.0 → 1.1.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 +11 -0
- package/dist/es/index.css +15 -8
- package/dist/es/index.js +225 -194
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +15 -8
- package/dist/index.js +229 -196
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/number-input.jsx +2 -2
- package/src/components/simple-keypad-input.jsx +18 -21
- package/src/index.js +1 -1
- package/src/multi-items/__tests__/__snapshots__/multi-renderer_test.js.snap +356 -65
- package/src/multi-items/__tests__/multi-renderer_test.js +9 -9
- package/src/perseus-types.js +18 -7
- package/src/styles/perseus-renderer.less +8 -2
- package/src/styles/widgets/passage.less +9 -5
- package/src/util/answer-types.js +2 -3
- package/src/util/math.js +3 -10
- package/src/widgets/__stories__/numeric-input.stories.jsx +43 -2
- package/src/widgets/__tests__/__snapshots__/definition_test.js.snap +4 -4
- package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +226 -65
- package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +2581 -679
- package/src/widgets/__tests__/group_test.jsx +4 -4
- package/src/widgets/__tests__/numeric-input_test.jsx +12 -8
- package/src/widgets/__tests__/passage_test.jsx +81 -0
- package/src/widgets/__tests__/radio/base-radio_test.jsx +55 -7
- package/src/widgets/__tests__/radio/choice_test.jsx +164 -63
- package/src/widgets/__tests__/radio_test.jsx +39 -15
- package/src/widgets/definition.jsx +1 -1
- package/src/widgets/group.jsx +84 -102
- package/src/widgets/input-number.jsx +6 -1
- package/src/widgets/numeric-input.jsx +87 -80
- package/src/widgets/radio/base-radio.jsx +3 -1
- package/src/widgets/radio/choice-a11y-text.js +62 -0
- package/src/widgets/radio/choice-icon/choice-icon.jsx +0 -68
- package/src/widgets/radio/choice-icon/library-choice-icon.jsx +5 -10
- package/src/widgets/radio/choice-icon/sat-choice-icon.jsx +3 -12
- package/src/widgets/radio/choice.jsx +46 -3
|
@@ -136,7 +136,7 @@ describe("multi-item renderer", () => {
|
|
|
136
136
|
// selected, a value is entered). You can see the result of this in the `choiceStates`
|
|
137
137
|
// array in the captured state below where the choice at index 2 has
|
|
138
138
|
// `"selected": true` (instead of false) and the input-number has a `currentValue`.
|
|
139
|
-
userEvent.click(screen.getAllByRole("
|
|
139
|
+
userEvent.click(screen.getAllByRole("radio")[2]); // Correct
|
|
140
140
|
userEvent.paste(screen.getByRole("textbox"), "+42"); // Correct
|
|
141
141
|
|
|
142
142
|
// Act
|
|
@@ -268,7 +268,7 @@ describe("multi-item renderer", () => {
|
|
|
268
268
|
// Arrange
|
|
269
269
|
const {renderer} = renderSimpleQuestion(question1);
|
|
270
270
|
|
|
271
|
-
userEvent.click(screen.getAllByRole("
|
|
271
|
+
userEvent.click(screen.getAllByRole("radio")[2]);
|
|
272
272
|
userEvent.paste(screen.getByRole("textbox"), "99");
|
|
273
273
|
|
|
274
274
|
// Act
|
|
@@ -525,11 +525,11 @@ describe("multi-item renderer", () => {
|
|
|
525
525
|
renderer.restoreSerializedState(state);
|
|
526
526
|
|
|
527
527
|
// Assert
|
|
528
|
-
expect(screen.getAllByRole("
|
|
529
|
-
expect(screen.getAllByRole("
|
|
530
|
-
expect(screen.getAllByRole("
|
|
531
|
-
expect(screen.getAllByRole("
|
|
532
|
-
expect(screen.getAllByRole("
|
|
528
|
+
expect(screen.getAllByRole("radio")[0]).not.toBeChecked();
|
|
529
|
+
expect(screen.getAllByRole("radio")[1]).not.toBeChecked();
|
|
530
|
+
expect(screen.getAllByRole("radio")[2]).toBeChecked();
|
|
531
|
+
expect(screen.getAllByRole("radio")[3]).not.toBeChecked();
|
|
532
|
+
expect(screen.getAllByRole("radio")[4]).not.toBeChecked();
|
|
533
533
|
expect(screen.getByRole("textbox")).toHaveValue("+42");
|
|
534
534
|
});
|
|
535
535
|
|
|
@@ -586,7 +586,7 @@ describe("multi-item renderer", () => {
|
|
|
586
586
|
// Arrange
|
|
587
587
|
const {renderer} = renderSimpleQuestion(question1);
|
|
588
588
|
|
|
589
|
-
userEvent.click(screen.getAllByRole("
|
|
589
|
+
userEvent.click(screen.getAllByRole("radio")[3]); // Correct
|
|
590
590
|
userEvent.paste(screen.getByRole("textbox"), "-42"); // Correct
|
|
591
591
|
|
|
592
592
|
// Act
|
|
@@ -747,7 +747,7 @@ describe("multi-item renderer", () => {
|
|
|
747
747
|
// Arrange
|
|
748
748
|
const {renderer} = renderSimpleQuestion(question1);
|
|
749
749
|
|
|
750
|
-
userEvent.click(screen.getAllByRole("
|
|
750
|
+
userEvent.click(screen.getAllByRole("radio")[3]); // Correct
|
|
751
751
|
userEvent.paste(screen.getByRole("textbox"), "-42"); // Correct
|
|
752
752
|
|
|
753
753
|
// Act
|
package/src/perseus-types.js
CHANGED
|
@@ -706,6 +706,20 @@ export type PerseusMeasurerWidgetOptions = {|
|
|
|
706
706
|
static: boolean,
|
|
707
707
|
|};
|
|
708
708
|
|
|
709
|
+
export type MathFormat =
|
|
710
|
+
| "integer"
|
|
711
|
+
| "mixed"
|
|
712
|
+
| "improper"
|
|
713
|
+
| "proper"
|
|
714
|
+
| "decimal"
|
|
715
|
+
| "percent"
|
|
716
|
+
| "pi";
|
|
717
|
+
|
|
718
|
+
export type PerseusNumericInputAnswerForm = {|
|
|
719
|
+
simplify: ?("required" | "correct" | "enforced" | "optional"),
|
|
720
|
+
name: MathFormat,
|
|
721
|
+
|};
|
|
722
|
+
|
|
709
723
|
export type PerseusNumericInputWidgetOptions = {|
|
|
710
724
|
// A list of all the possible correct and incorrect answers
|
|
711
725
|
answers: $ReadOnlyArray<PerseusNumericInputAnswer>,
|
|
@@ -726,22 +740,19 @@ export type PerseusNumericInputWidgetOptions = {|
|
|
|
726
740
|
|
|
727
741
|
// Used by examples, maybe not used and should be removed in the future
|
|
728
742
|
// see TODO in numeric-input
|
|
729
|
-
answerForms?: $ReadOnlyArray<
|
|
730
|
-
simplify: ?("required" | "correct" | "enforced"),
|
|
731
|
-
name: "integer" | "decimal" | "proper" | "improper" | "mixed" | "pi",
|
|
732
|
-
|}>,
|
|
743
|
+
answerForms?: $ReadOnlyArray<PerseusNumericInputAnswerForm>,
|
|
733
744
|
|};
|
|
734
745
|
|
|
735
746
|
export type PerseusNumericInputAnswer = {|
|
|
736
747
|
// Translatable Display; A description for why this answer is correct, wrong, or ungraded
|
|
737
748
|
message: string,
|
|
738
749
|
// The expected answer
|
|
739
|
-
value:
|
|
750
|
+
value: number,
|
|
740
751
|
// Whether this answer is "correct", "wrong", or "ungraded"
|
|
741
752
|
status: string,
|
|
742
753
|
// The forms available for this answer. Options: "integer, ""decimal", "proper", "improper", "mixed", or "pi"
|
|
743
754
|
// NOTE: perseus_data.go says this is required even though it isn't necessary.
|
|
744
|
-
answerForms?: $ReadOnlyArray<
|
|
755
|
+
answerForms?: $ReadOnlyArray<MathFormat>,
|
|
745
756
|
// Whether the answerForms should be strictly matched
|
|
746
757
|
strict: boolean,
|
|
747
758
|
// A range of error +/- the value
|
|
@@ -1404,7 +1415,7 @@ export type PerseusInputNumberWidgetOptions = {|
|
|
|
1404
1415
|
rightAlign?: boolean,
|
|
1405
1416
|
simplify: "required" | "optional" | "enforced",
|
|
1406
1417
|
size: "normal" | "small",
|
|
1407
|
-
value: string | number
|
|
1418
|
+
value: string | number,
|
|
1408
1419
|
customKeypad?: boolean,
|
|
1409
1420
|
|};
|
|
1410
1421
|
|
|
@@ -107,7 +107,10 @@
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
div.paragraph {
|
|
110
|
-
|
|
110
|
+
font-family: "Noto Serif", serif;
|
|
111
|
+
font-weight: 400;
|
|
112
|
+
font-size: 18px;
|
|
113
|
+
line-height: 22px;
|
|
111
114
|
margin: 22px 0px;
|
|
112
115
|
}
|
|
113
116
|
|
|
@@ -131,8 +134,11 @@
|
|
|
131
134
|
|
|
132
135
|
div.instructions {
|
|
133
136
|
display: block;
|
|
137
|
+
font-family: "Noto Serif", serif;
|
|
138
|
+
font-weight: 800;
|
|
139
|
+
font-size: 18px;
|
|
140
|
+
line-height: 22px;
|
|
134
141
|
font-style: italic;
|
|
135
|
-
font-weight: bold;
|
|
136
142
|
}
|
|
137
143
|
|
|
138
144
|
.perseus-renderer > .paragraph > ul:not(.perseus-widget-radio),
|
|
@@ -38,15 +38,19 @@
|
|
|
38
38
|
line-height: @line-height - 2;
|
|
39
39
|
}
|
|
40
40
|
.passage-title div.paragraph {
|
|
41
|
-
font-
|
|
41
|
+
font-family: "Noto Serif", serif;
|
|
42
42
|
font-weight: 700;
|
|
43
|
+
font-size: 20px;
|
|
44
|
+
line-height: 22px;
|
|
45
|
+
|
|
43
46
|
margin: 0 0 10px;
|
|
44
47
|
}
|
|
45
48
|
.passage-text div.paragraph {
|
|
46
|
-
font-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
font-family: "Noto Serif", serif;
|
|
50
|
+
font-weight: 400;
|
|
51
|
+
font-size: 20px;
|
|
52
|
+
line-height: 24px;
|
|
53
|
+
|
|
50
54
|
span {
|
|
51
55
|
text-indent: 0;
|
|
52
56
|
}
|
package/src/util/answer-types.js
CHANGED
|
@@ -106,7 +106,7 @@ export const errors = {
|
|
|
106
106
|
*
|
|
107
107
|
*/
|
|
108
108
|
|
|
109
|
-
const KhanAnswerTypes
|
|
109
|
+
const KhanAnswerTypes = {
|
|
110
110
|
/*
|
|
111
111
|
* predicate answer type
|
|
112
112
|
*
|
|
@@ -653,8 +653,7 @@ const KhanAnswerTypes: $FlowFixMe = {
|
|
|
653
653
|
correctAnswer: string,
|
|
654
654
|
options: $FlowFixMe,
|
|
655
655
|
): Predicate {
|
|
656
|
-
|
|
657
|
-
const correctFloat = parseFloat($.trim(correctAnswer));
|
|
656
|
+
const correctFloat = parseFloat(correctAnswer);
|
|
658
657
|
|
|
659
658
|
return [
|
|
660
659
|
function (guess, maxError) {
|
package/src/util/math.js
CHANGED
|
@@ -4,14 +4,7 @@ import {number as knumber} from "@khanacademy/kmath";
|
|
|
4
4
|
import $ from "jquery";
|
|
5
5
|
import _ from "underscore";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
| "integer"
|
|
9
|
-
| "mixed"
|
|
10
|
-
| "improper"
|
|
11
|
-
| "proper"
|
|
12
|
-
| "decimal"
|
|
13
|
-
| "percent"
|
|
14
|
-
| "pi";
|
|
7
|
+
import type {MathFormat} from "../perseus-types";
|
|
15
8
|
|
|
16
9
|
const KhanMath = {
|
|
17
10
|
// Simplify formulas before display
|
|
@@ -213,7 +206,7 @@ const KhanMath = {
|
|
|
213
206
|
// Returns the format (string) of a given numeric string
|
|
214
207
|
// Note: purposively more inclusive than answer-types' predicate.forms
|
|
215
208
|
// That is, it is not necessarily true that interpreted input are numeric
|
|
216
|
-
getNumericFormat: function (text: string): ?
|
|
209
|
+
getNumericFormat: function (text: string): ?MathFormat {
|
|
217
210
|
text = $.trim(text);
|
|
218
211
|
text = text.replace(/\u2212/, "-").replace(/([+-])\s+/g, "$1");
|
|
219
212
|
if (text.match(/^[+-]?\d+$/)) {
|
|
@@ -238,7 +231,7 @@ const KhanMath = {
|
|
|
238
231
|
},
|
|
239
232
|
|
|
240
233
|
// Returns a string of the number in a specified format
|
|
241
|
-
toNumericString: function (number: number, format?:
|
|
234
|
+
toNumericString: function (number: number, format?: MathFormat): string {
|
|
242
235
|
if (number == null) {
|
|
243
236
|
return "";
|
|
244
237
|
}
|
|
@@ -1,19 +1,60 @@
|
|
|
1
1
|
// @flow
|
|
2
|
+
import {action} from "@storybook/addon-actions";
|
|
2
3
|
import * as React from "react";
|
|
3
4
|
|
|
4
5
|
import {RendererWithDebugUI} from "../../../../../testing/renderer-with-debug-ui.jsx";
|
|
5
6
|
import {question1} from "../__testdata__/numeric-input_testdata.js";
|
|
7
|
+
import {NumericInput} from "../numeric-input.jsx";
|
|
6
8
|
|
|
7
|
-
type StoryArgs = {
|
|
9
|
+
type StoryArgs = {|
|
|
10
|
+
currentValue: string,
|
|
11
|
+
|};
|
|
8
12
|
|
|
9
13
|
type Story = {|
|
|
10
14
|
title: string,
|
|
15
|
+
args: StoryArgs,
|
|
11
16
|
|};
|
|
12
17
|
|
|
18
|
+
function generateProps(overwrite) {
|
|
19
|
+
const base = {
|
|
20
|
+
alignment: "",
|
|
21
|
+
answers: [],
|
|
22
|
+
containerSizeClass: "medium",
|
|
23
|
+
isLastUsedWidget: true,
|
|
24
|
+
currentValue: "",
|
|
25
|
+
problemNum: 0,
|
|
26
|
+
reviewModeRubric: {
|
|
27
|
+
answers: [],
|
|
28
|
+
labelText: "",
|
|
29
|
+
size: "medium",
|
|
30
|
+
coefficient: false,
|
|
31
|
+
static: false,
|
|
32
|
+
},
|
|
33
|
+
static: false,
|
|
34
|
+
widgetId: "widgetId",
|
|
35
|
+
findWidgets: action("findWidgets"),
|
|
36
|
+
onBlur: action("onBlur"),
|
|
37
|
+
onChange: action("onChange"),
|
|
38
|
+
onFocus: action("onFocus"),
|
|
39
|
+
trackInteraction: action("trackInteraction"),
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return {...base, ...overwrite};
|
|
43
|
+
}
|
|
44
|
+
|
|
13
45
|
export default ({
|
|
14
46
|
title: "Perseus/Widgets/NumericInput",
|
|
47
|
+
args: {
|
|
48
|
+
currentValue: "8675309",
|
|
49
|
+
},
|
|
15
50
|
}: Story);
|
|
16
51
|
|
|
17
|
-
export const Question1 = (
|
|
52
|
+
export const Question1 = (): React.Node => {
|
|
18
53
|
return <RendererWithDebugUI question={question1} />;
|
|
19
54
|
};
|
|
55
|
+
|
|
56
|
+
export const Interactive = (args: StoryArgs): React.Node => {
|
|
57
|
+
const props = generateProps(args);
|
|
58
|
+
|
|
59
|
+
return <NumericInput {...props} />;
|
|
60
|
+
};
|
|
@@ -37,13 +37,13 @@ exports[`Definition widget should have a default snapshot: first render 1`] = `
|
|
|
37
37
|
>
|
|
38
38
|
<button
|
|
39
39
|
aria-disabled="false"
|
|
40
|
-
class="button_vr44p2-o_O-shared_lwskrm-o_O-default_1hl5pu8
|
|
40
|
+
class="button_vr44p2-o_O-shared_lwskrm-o_O-default_1hl5pu8"
|
|
41
41
|
role="button"
|
|
42
42
|
tabindex="0"
|
|
43
43
|
type="button"
|
|
44
44
|
>
|
|
45
45
|
<span
|
|
46
|
-
class="text_f1191h-o_O-
|
|
46
|
+
class="text_f1191h-o_O-LabelLarge_5s82ln-o_O-text_m8a8ki-o_O-textWithFocus_e296pg"
|
|
47
47
|
>
|
|
48
48
|
the Pequots
|
|
49
49
|
</span>
|
|
@@ -95,13 +95,13 @@ exports[`Definition widget should have an open state snapshot: open state 1`] =
|
|
|
95
95
|
>
|
|
96
96
|
<button
|
|
97
97
|
aria-disabled="false"
|
|
98
|
-
class="button_vr44p2-o_O-shared_lwskrm-o_O-default_1hl5pu8
|
|
98
|
+
class="button_vr44p2-o_O-shared_lwskrm-o_O-default_1hl5pu8"
|
|
99
99
|
role="button"
|
|
100
100
|
tabindex="0"
|
|
101
101
|
type="button"
|
|
102
102
|
>
|
|
103
103
|
<span
|
|
104
|
-
class="text_f1191h-o_O-
|
|
104
|
+
class="text_f1191h-o_O-LabelLarge_5s82ln-o_O-text_m8a8ki-o_O-textWithFocus_e296pg-o_O-focus_15mc0l2"
|
|
105
105
|
>
|
|
106
106
|
the Pequots
|
|
107
107
|
</span>
|