@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.
Files changed (38) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/es/index.css +15 -8
  3. package/dist/es/index.js +225 -194
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/index.css +15 -8
  6. package/dist/index.js +229 -196
  7. package/dist/index.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/components/number-input.jsx +2 -2
  10. package/src/components/simple-keypad-input.jsx +18 -21
  11. package/src/index.js +1 -1
  12. package/src/multi-items/__tests__/__snapshots__/multi-renderer_test.js.snap +356 -65
  13. package/src/multi-items/__tests__/multi-renderer_test.js +9 -9
  14. package/src/perseus-types.js +18 -7
  15. package/src/styles/perseus-renderer.less +8 -2
  16. package/src/styles/widgets/passage.less +9 -5
  17. package/src/util/answer-types.js +2 -3
  18. package/src/util/math.js +3 -10
  19. package/src/widgets/__stories__/numeric-input.stories.jsx +43 -2
  20. package/src/widgets/__tests__/__snapshots__/definition_test.js.snap +4 -4
  21. package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +226 -65
  22. package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +2581 -679
  23. package/src/widgets/__tests__/group_test.jsx +4 -4
  24. package/src/widgets/__tests__/numeric-input_test.jsx +12 -8
  25. package/src/widgets/__tests__/passage_test.jsx +81 -0
  26. package/src/widgets/__tests__/radio/base-radio_test.jsx +55 -7
  27. package/src/widgets/__tests__/radio/choice_test.jsx +164 -63
  28. package/src/widgets/__tests__/radio_test.jsx +39 -15
  29. package/src/widgets/definition.jsx +1 -1
  30. package/src/widgets/group.jsx +84 -102
  31. package/src/widgets/input-number.jsx +6 -1
  32. package/src/widgets/numeric-input.jsx +87 -80
  33. package/src/widgets/radio/base-radio.jsx +3 -1
  34. package/src/widgets/radio/choice-a11y-text.js +62 -0
  35. package/src/widgets/radio/choice-icon/choice-icon.jsx +0 -68
  36. package/src/widgets/radio/choice-icon/library-choice-icon.jsx +5 -10
  37. package/src/widgets/radio/choice-icon/sat-choice-icon.jsx +3 -12
  38. 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("checkbox")[2]); // Correct
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("checkbox")[2]);
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("checkbox")[0]).not.toBeChecked();
529
- expect(screen.getAllByRole("checkbox")[1]).not.toBeChecked();
530
- expect(screen.getAllByRole("checkbox")[2]).toBeChecked();
531
- expect(screen.getAllByRole("checkbox")[3]).not.toBeChecked();
532
- expect(screen.getAllByRole("checkbox")[4]).not.toBeChecked();
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("checkbox")[3]); // Correct
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("checkbox")[3]); // Correct
750
+ userEvent.click(screen.getAllByRole("radio")[3]); // Correct
751
751
  userEvent.paste(screen.getByRole("textbox"), "-42"); // Correct
752
752
 
753
753
  // Act
@@ -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: ?number,
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<string>,
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 | boolean,
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
- .legacy-typography;
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-size: @font-size;
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-size: @font-size;
47
- line-height: @line-height;
48
- margin: 0;
49
- text-indent: 20px;
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
  }
@@ -106,7 +106,7 @@ export const errors = {
106
106
  *
107
107
  */
108
108
 
109
- const KhanAnswerTypes: $FlowFixMe = {
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
- // TODO(alpert): Don't think this $.trim is necessary
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
- export type Format =
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): ?Format {
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?: Format): string {
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 = (args: StoryArgs): React.Node => {
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-o_O-small_xwkmkn"
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-LabelSmall_hl4zs1-o_O-text_m8a8ki-o_O-textWithFocus_e296pg"
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-o_O-small_xwkmkn"
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-LabelSmall_hl4zs1-o_O-text_m8a8ki-o_O-textWithFocus_e296pg-o_O-focus_15mc0l2"
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>