@khanacademy/perseus 13.1.0 → 14.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 +43 -0
- package/dist/components/svg-image.d.ts +10 -9
- package/dist/es/index.css +0 -7
- package/dist/es/index.js +37 -31
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +0 -7
- package/dist/index.js +61 -49
- package/dist/index.js.map +1 -1
- package/dist/renderer.d.ts +2 -2
- package/dist/widgets/matcher.d.ts +1 -1
- package/package.json +35 -35
- package/src/__stories__/hints-renderer.stories.tsx +41 -44
- package/src/__tests__/__snapshots__/renderer.test.tsx.snap +9 -33
- package/src/__tests__/article-renderer.test.tsx +2 -3
- package/src/__tests__/renderer-api.test.tsx +35 -52
- package/src/components/graph.tsx +14 -8
- package/src/components/svg-image.tsx +13 -11
- package/src/mixins/provide-keypad.tsx +0 -1
- package/src/perseus-types.ts +8 -3
- package/src/renderer.tsx +2 -2
- package/src/styles/khan-exercise.css +0 -2
- package/src/styles/perseus-renderer.less +0 -7
- package/src/widgets/__stories__/random-widgets.stories.tsx +59 -0
- package/src/widgets/__stories__/test-keypad-context-wrapper.tsx +0 -1
- package/src/widgets/__testdata__/categorizer.testdata.ts +40 -0
- package/src/widgets/__testdata__/dropdown.testdata.ts +38 -0
- package/src/widgets/__testdata__/explanation.testdata.ts +30 -0
- package/src/widgets/__testdata__/expression.testdata.ts +82 -0
- package/src/widgets/__testdata__/interactive-graph-random.testdata.ts +269 -0
- package/src/widgets/__testdata__/radio.testdata.ts +63 -0
- package/src/widgets/__testdata__/randomizers.ts +51 -0
- package/src/widgets/__tests__/__snapshots__/dropdown.test.ts.snap +6 -22
- package/src/widgets/__tests__/__snapshots__/graded-group-set-jipt.test.ts.snap +3 -3
- package/src/widgets/__tests__/__snapshots__/graded-group-set.test.ts.snap +1 -1
- package/src/widgets/__tests__/__snapshots__/graded-group.test.ts.snap +1 -1
- package/src/widgets/__tests__/__snapshots__/radio.test.ts.snap +4 -4
- package/src/widgets/__tests__/expression-mobile.test.tsx +0 -1
- package/src/widgets/__tests__/matcher.test.tsx +1 -1
- package/src/widgets/__tests__/randomizers.test.ts +53 -0
- package/src/widgets/image.tsx +27 -20
- package/src/widgets/matcher.tsx +1 -1
- package/src/widgets/radio/choice.tsx +1 -0
- package/tsconfig-build.tsbuildinfo +1 -1
package/dist/renderer.d.ts
CHANGED
|
@@ -33,7 +33,7 @@ export type Widget = {
|
|
|
33
33
|
blurInputPath?: (path: FocusPath) => null;
|
|
34
34
|
focusInputPath?: (path: FocusPath) => null;
|
|
35
35
|
getInputPaths?: () => ReadonlyArray<FocusPath>;
|
|
36
|
-
setInputValue?: (path: FocusPath, newValue: string, focus
|
|
36
|
+
setInputValue?: (path: FocusPath, newValue: string, focus?: () => unknown) => void;
|
|
37
37
|
getUserInput?: () => WidgetUserInput | null | undefined;
|
|
38
38
|
simpleValidate?: (options?: any, onOutputError?: (widgetId: any, value: string, message?: string | null | undefined) => unknown | null | undefined) => PerseusScore;
|
|
39
39
|
showRationalesForCurrentlySelectedChoices?: (options?: any) => void;
|
|
@@ -210,7 +210,7 @@ declare class Renderer extends React.Component<Props, State> {
|
|
|
210
210
|
serialize: () => Record<any, any>;
|
|
211
211
|
emptyWidgets: () => any;
|
|
212
212
|
_setWidgetProps: SetWidgetPropsFn;
|
|
213
|
-
setInputValue: (path: FocusPath, newValue: string, focus
|
|
213
|
+
setInputValue: (path: FocusPath, newValue: string, focus?: () => unknown) => void;
|
|
214
214
|
/**
|
|
215
215
|
* Returns an array of the widget `.getUserInput()` results
|
|
216
216
|
*/
|
|
@@ -6,7 +6,7 @@ type State = {
|
|
|
6
6
|
rightHeight: number;
|
|
7
7
|
texRendererLoaded: boolean;
|
|
8
8
|
};
|
|
9
|
-
declare class Matcher extends React.Component<any, any> {
|
|
9
|
+
export declare class Matcher extends React.Component<any, any> {
|
|
10
10
|
static propTypes: {
|
|
11
11
|
apiOptions: PropTypes.Validator<NonNullable<PropTypes.InferProps<{
|
|
12
12
|
isArticle: PropTypes.Validator<boolean>;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Core Perseus API (includes renderers and widgets)",
|
|
4
4
|
"author": "Khan Academy",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "
|
|
6
|
+
"version": "14.0.0",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
@@ -24,29 +24,29 @@
|
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@khanacademy/kas": "^0.3.5",
|
|
26
26
|
"@khanacademy/kmath": "^0.1.6",
|
|
27
|
-
"@khanacademy/math-input": "^
|
|
27
|
+
"@khanacademy/math-input": "^16.0.0",
|
|
28
28
|
"@khanacademy/perseus-core": "1.3.0",
|
|
29
29
|
"@khanacademy/perseus-linter": "^0.3.8",
|
|
30
|
-
"@khanacademy/pure-markdown": "^0.2.
|
|
31
|
-
"@khanacademy/simple-markdown": "^0.
|
|
30
|
+
"@khanacademy/pure-markdown": "^0.2.11",
|
|
31
|
+
"@khanacademy/simple-markdown": "^0.11.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@khanacademy/wonder-blocks-button": "^
|
|
35
|
-
"@khanacademy/wonder-blocks-clickable": "^4.0.
|
|
36
|
-
"@khanacademy/wonder-blocks-color": "^
|
|
37
|
-
"@khanacademy/wonder-blocks-core": "^6.
|
|
38
|
-
"@khanacademy/wonder-blocks-data": "^
|
|
39
|
-
"@khanacademy/wonder-blocks-dropdown": "^3.
|
|
40
|
-
"@khanacademy/wonder-blocks-form": "^4.3.
|
|
34
|
+
"@khanacademy/wonder-blocks-button": "^6.2.2",
|
|
35
|
+
"@khanacademy/wonder-blocks-clickable": "^4.0.11",
|
|
36
|
+
"@khanacademy/wonder-blocks-color": "^3.0.0",
|
|
37
|
+
"@khanacademy/wonder-blocks-core": "^6.3.0",
|
|
38
|
+
"@khanacademy/wonder-blocks-data": "^13.0.5",
|
|
39
|
+
"@khanacademy/wonder-blocks-dropdown": "^3.1.8",
|
|
40
|
+
"@khanacademy/wonder-blocks-form": "^4.3.16",
|
|
41
41
|
"@khanacademy/wonder-blocks-i18n": "^2.0.2",
|
|
42
|
-
"@khanacademy/wonder-blocks-layout": "^2.0.
|
|
43
|
-
"@khanacademy/wonder-blocks-link": "^
|
|
44
|
-
"@khanacademy/wonder-blocks-modal": "^4.0.
|
|
45
|
-
"@khanacademy/wonder-blocks-popover": "^3.0.
|
|
46
|
-
"@khanacademy/wonder-blocks-progress-spinner": "^2.0.
|
|
47
|
-
"@khanacademy/wonder-blocks-spacing": "^
|
|
48
|
-
"@khanacademy/wonder-blocks-tooltip": "^2.1.
|
|
49
|
-
"@khanacademy/wonder-blocks-typography": "^2.1.
|
|
42
|
+
"@khanacademy/wonder-blocks-layout": "^2.0.24",
|
|
43
|
+
"@khanacademy/wonder-blocks-link": "^6.0.2",
|
|
44
|
+
"@khanacademy/wonder-blocks-modal": "^4.0.35",
|
|
45
|
+
"@khanacademy/wonder-blocks-popover": "^3.0.16",
|
|
46
|
+
"@khanacademy/wonder-blocks-progress-spinner": "^2.0.24",
|
|
47
|
+
"@khanacademy/wonder-blocks-spacing": "^4.0.1",
|
|
48
|
+
"@khanacademy/wonder-blocks-tooltip": "^2.1.18",
|
|
49
|
+
"@khanacademy/wonder-blocks-typography": "^2.1.9",
|
|
50
50
|
"@khanacademy/wonder-stuff-core": "^1.5.1",
|
|
51
51
|
"@phosphor-icons/core": "^2.0.2",
|
|
52
52
|
"aphrodite": "^1.2.5",
|
|
@@ -59,29 +59,29 @@
|
|
|
59
59
|
"katex": "^0.11.1",
|
|
60
60
|
"lodash.debounce": "^4.0.8",
|
|
61
61
|
"mathquill": "git+https://git@github.com/Khan/mathquill.git#48410e80d760bbd5105544d4a4ab459a28dc2cbc",
|
|
62
|
-
"perseus-build-settings": "^0.
|
|
62
|
+
"perseus-build-settings": "^0.3.0",
|
|
63
63
|
"prop-types": "^15.6.1",
|
|
64
64
|
"react": "16.14.0",
|
|
65
65
|
"react-dom": "16.14.0",
|
|
66
66
|
"underscore": "^1.4.4"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
|
-
"@khanacademy/wonder-blocks-button": "^
|
|
70
|
-
"@khanacademy/wonder-blocks-clickable": "^4.0.
|
|
71
|
-
"@khanacademy/wonder-blocks-color": "^
|
|
72
|
-
"@khanacademy/wonder-blocks-core": "^6.
|
|
73
|
-
"@khanacademy/wonder-blocks-data": "^
|
|
74
|
-
"@khanacademy/wonder-blocks-dropdown": "^3.
|
|
75
|
-
"@khanacademy/wonder-blocks-form": "^4.3.
|
|
69
|
+
"@khanacademy/wonder-blocks-button": "^6.2.2",
|
|
70
|
+
"@khanacademy/wonder-blocks-clickable": "^4.0.11",
|
|
71
|
+
"@khanacademy/wonder-blocks-color": "^3.0.0",
|
|
72
|
+
"@khanacademy/wonder-blocks-core": "^6.3.0",
|
|
73
|
+
"@khanacademy/wonder-blocks-data": "^13.0.5",
|
|
74
|
+
"@khanacademy/wonder-blocks-dropdown": "^3.1.8",
|
|
75
|
+
"@khanacademy/wonder-blocks-form": "^4.3.16",
|
|
76
76
|
"@khanacademy/wonder-blocks-i18n": "^2.0.2",
|
|
77
|
-
"@khanacademy/wonder-blocks-layout": "^2.0.
|
|
78
|
-
"@khanacademy/wonder-blocks-link": "^
|
|
79
|
-
"@khanacademy/wonder-blocks-modal": "^4.0.
|
|
80
|
-
"@khanacademy/wonder-blocks-popover": "^3.0.
|
|
81
|
-
"@khanacademy/wonder-blocks-progress-spinner": "^2.0.
|
|
82
|
-
"@khanacademy/wonder-blocks-spacing": "^
|
|
83
|
-
"@khanacademy/wonder-blocks-tooltip": "^2.1.
|
|
84
|
-
"@khanacademy/wonder-blocks-typography": "^2.1.
|
|
77
|
+
"@khanacademy/wonder-blocks-layout": "^2.0.24",
|
|
78
|
+
"@khanacademy/wonder-blocks-link": "^6.0.2",
|
|
79
|
+
"@khanacademy/wonder-blocks-modal": "^4.0.35",
|
|
80
|
+
"@khanacademy/wonder-blocks-popover": "^3.0.16",
|
|
81
|
+
"@khanacademy/wonder-blocks-progress-spinner": "^2.0.24",
|
|
82
|
+
"@khanacademy/wonder-blocks-spacing": "^4.0.1",
|
|
83
|
+
"@khanacademy/wonder-blocks-tooltip": "^2.1.18",
|
|
84
|
+
"@khanacademy/wonder-blocks-typography": "^2.1.9",
|
|
85
85
|
"@khanacademy/wonder-stuff-core": "^1.5.1",
|
|
86
86
|
"@phosphor-icons/core": "^2.0.2",
|
|
87
87
|
"aphrodite": "^1.2.5",
|
|
@@ -3,56 +3,53 @@ import React from "react";
|
|
|
3
3
|
|
|
4
4
|
import HintsRenderer from "../hints-renderer";
|
|
5
5
|
|
|
6
|
-
type
|
|
7
|
-
hints: ReadonlyArray<any>;
|
|
8
|
-
hintsVisible: number;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
type Story = {
|
|
12
|
-
title: string;
|
|
13
|
-
argTypes: any;
|
|
14
|
-
};
|
|
6
|
+
import type {Meta, StoryObj} from "@storybook/react";
|
|
15
7
|
|
|
16
|
-
|
|
8
|
+
const meta: Meta<typeof HintsRenderer> = {
|
|
17
9
|
title: "Perseus/Renderers/Hints Renderer",
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
{
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
widgets: {},
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
content: "this is hint 2",
|
|
30
|
-
images: {},
|
|
31
|
-
replace: false,
|
|
32
|
-
widgets: {},
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
content: "this is hint 3",
|
|
36
|
-
images: {},
|
|
37
|
-
replace: false,
|
|
38
|
-
widgets: {},
|
|
39
|
-
},
|
|
40
|
-
],
|
|
10
|
+
component: HintsRenderer,
|
|
11
|
+
decorators: [
|
|
12
|
+
(Story) => {
|
|
13
|
+
return (
|
|
14
|
+
<View style={{left: 80}}>
|
|
15
|
+
<Story />
|
|
16
|
+
</View>
|
|
17
|
+
);
|
|
41
18
|
},
|
|
19
|
+
],
|
|
20
|
+
argTypes: {
|
|
42
21
|
hintsVisible: {
|
|
43
|
-
control: {
|
|
22
|
+
control: {min: 0},
|
|
44
23
|
defaultValue: 3,
|
|
45
24
|
},
|
|
46
25
|
},
|
|
47
|
-
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default meta;
|
|
48
29
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
30
|
+
type Story = StoryObj<typeof HintsRenderer>;
|
|
31
|
+
|
|
32
|
+
export const Interactive: Story = {
|
|
33
|
+
args: {
|
|
34
|
+
hints: [
|
|
35
|
+
{
|
|
36
|
+
content: "this is hint 1",
|
|
37
|
+
images: {},
|
|
38
|
+
replace: false,
|
|
39
|
+
widgets: {},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
content: "this is hint 2",
|
|
43
|
+
images: {},
|
|
44
|
+
replace: false,
|
|
45
|
+
widgets: {},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
content: "this is hint 3",
|
|
49
|
+
images: {},
|
|
50
|
+
replace: false,
|
|
51
|
+
widgets: {},
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
},
|
|
58
55
|
};
|
|
@@ -367,18 +367,10 @@ exports[`renderer snapshots correct answer: correct answer 1`] = `
|
|
|
367
367
|
>
|
|
368
368
|
less than or equal to
|
|
369
369
|
</span>
|
|
370
|
-
<
|
|
370
|
+
<span
|
|
371
371
|
aria-hidden="true"
|
|
372
|
-
class="svg_aeiopc-o_O-caret_ntkmq"
|
|
373
|
-
|
|
374
|
-
viewBox="0 0 16 16"
|
|
375
|
-
width="16"
|
|
376
|
-
>
|
|
377
|
-
<path
|
|
378
|
-
d="M8 8.586l3.293-3.293a1 1 0 0 1 1.414 1.414l-4 4a1 1 0 0 1-1.414 0l-4-4a1 1 0 0 1 1.414-1.414L8 8.586z"
|
|
379
|
-
fill="rgba(33,36,44,0.64)"
|
|
380
|
-
/>
|
|
381
|
-
</svg>
|
|
372
|
+
class="svg_aeiopc-o_O-caret_ntkmq-o_O-inlineStyles_13xtep5"
|
|
373
|
+
/>
|
|
382
374
|
</button>
|
|
383
375
|
</div>
|
|
384
376
|
</div>
|
|
@@ -440,18 +432,10 @@ exports[`renderer snapshots incorrect answer: incorrect answer 1`] = `
|
|
|
440
432
|
>
|
|
441
433
|
greater than or equal to
|
|
442
434
|
</span>
|
|
443
|
-
<
|
|
435
|
+
<span
|
|
444
436
|
aria-hidden="true"
|
|
445
|
-
class="svg_aeiopc-o_O-caret_ntkmq"
|
|
446
|
-
|
|
447
|
-
viewBox="0 0 16 16"
|
|
448
|
-
width="16"
|
|
449
|
-
>
|
|
450
|
-
<path
|
|
451
|
-
d="M8 8.586l3.293-3.293a1 1 0 0 1 1.414 1.414l-4 4a1 1 0 0 1-1.414 0l-4-4a1 1 0 0 1 1.414-1.414L8 8.586z"
|
|
452
|
-
fill="rgba(33,36,44,0.64)"
|
|
453
|
-
/>
|
|
454
|
-
</svg>
|
|
437
|
+
class="svg_aeiopc-o_O-caret_ntkmq-o_O-inlineStyles_13xtep5"
|
|
438
|
+
/>
|
|
455
439
|
</button>
|
|
456
440
|
</div>
|
|
457
441
|
</div>
|
|
@@ -513,18 +497,10 @@ exports[`renderer snapshots initial render: initial render 1`] = `
|
|
|
513
497
|
>
|
|
514
498
|
greater/less than or equal to
|
|
515
499
|
</span>
|
|
516
|
-
<
|
|
500
|
+
<span
|
|
517
501
|
aria-hidden="true"
|
|
518
|
-
class="svg_aeiopc-o_O-caret_ntkmq"
|
|
519
|
-
|
|
520
|
-
viewBox="0 0 16 16"
|
|
521
|
-
width="16"
|
|
522
|
-
>
|
|
523
|
-
<path
|
|
524
|
-
d="M8 8.586l3.293-3.293a1 1 0 0 1 1.414 1.414l-4 4a1 1 0 0 1-1.414 0l-4-4a1 1 0 0 1 1.414-1.414L8 8.586z"
|
|
525
|
-
fill="rgba(33,36,44,0.64)"
|
|
526
|
-
/>
|
|
527
|
-
</svg>
|
|
502
|
+
class="svg_aeiopc-o_O-caret_ntkmq-o_O-inlineStyles_13xtep5"
|
|
503
|
+
/>
|
|
528
504
|
</button>
|
|
529
505
|
</div>
|
|
530
506
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
StatefulKeypadContextProvider,
|
|
3
3
|
KeypadContext,
|
|
4
|
+
MobileKeypad,
|
|
4
5
|
} from "@khanacademy/math-input";
|
|
5
6
|
import {RenderStateRoot} from "@khanacademy/wonder-blocks-core";
|
|
6
7
|
import {screen, render, fireEvent, waitFor} from "@testing-library/react";
|
|
@@ -11,7 +12,6 @@ import {
|
|
|
11
12
|
testDependencies,
|
|
12
13
|
testDependenciesV2,
|
|
13
14
|
} from "../../../../testing/test-dependencies";
|
|
14
|
-
import KeypadSwitch from "../../../math-input/src/components/keypad-switch";
|
|
15
15
|
import {articleWithExpression} from "../__testdata__/article-renderer.testdata";
|
|
16
16
|
import ArticleRenderer from "../article-renderer";
|
|
17
17
|
import * as Dependencies from "../dependencies";
|
|
@@ -24,11 +24,10 @@ function KeypadWithContext() {
|
|
|
24
24
|
<KeypadContext.Consumer>
|
|
25
25
|
{({setKeypadElement}) => {
|
|
26
26
|
return (
|
|
27
|
-
<
|
|
27
|
+
<MobileKeypad
|
|
28
28
|
onElementMounted={setKeypadElement}
|
|
29
29
|
onDismiss={() => {}}
|
|
30
30
|
onAnalyticsEvent={async () => {}}
|
|
31
|
-
useV2Keypad
|
|
32
31
|
/>
|
|
33
32
|
);
|
|
34
33
|
}}
|
|
@@ -1,26 +1,22 @@
|
|
|
1
1
|
import {describe, beforeEach, it} from "@jest/globals";
|
|
2
|
-
import {RenderStateRoot} from "@khanacademy/wonder-blocks-core";
|
|
3
|
-
import {mount} from "enzyme"; // eslint-disable-line no-restricted-imports
|
|
4
2
|
import $ from "jquery";
|
|
5
3
|
import * as React from "react";
|
|
6
4
|
import ReactDOM from "react-dom";
|
|
7
5
|
import TestUtils from "react-dom/test-utils";
|
|
8
6
|
import _ from "underscore";
|
|
9
|
-
import "jest-enzyme";
|
|
10
7
|
|
|
11
8
|
import {testDependencies} from "../../../../testing/test-dependencies";
|
|
12
9
|
import * as Dependencies from "../dependencies";
|
|
13
10
|
import {ClassNames} from "../perseus-api";
|
|
14
11
|
import Renderer from "../renderer";
|
|
15
12
|
import {registerAllWidgetsForTesting} from "../util/register-all-widgets-for-testing";
|
|
13
|
+
import {renderQuestion} from "../widgets/__tests__/renderQuestion";
|
|
16
14
|
|
|
17
15
|
import imageItem from "./test-items/image-item";
|
|
18
16
|
import inputNumber1Item from "./test-items/input-number-1-item";
|
|
19
17
|
import inputNumber2Item from "./test-items/input-number-2-item";
|
|
20
18
|
import tableItem from "./test-items/table-item";
|
|
21
19
|
|
|
22
|
-
import type {APIOptions} from "../types";
|
|
23
|
-
|
|
24
20
|
const itemWidget = inputNumber1Item;
|
|
25
21
|
|
|
26
22
|
// Returns a promise that will resolve shortly after the end of this
|
|
@@ -33,26 +29,6 @@ const delayedPromise = (value: undefined) => {
|
|
|
33
29
|
return deferred.promise();
|
|
34
30
|
};
|
|
35
31
|
|
|
36
|
-
const renderQuestionArea = function (item, apiOptions?: APIOptions): any {
|
|
37
|
-
const wrapper = mount(
|
|
38
|
-
<RenderStateRoot>
|
|
39
|
-
<Dependencies.DependenciesContext.Provider
|
|
40
|
-
value={{analytics: {onAnalyticsEvent: async () => undefined}}}
|
|
41
|
-
>
|
|
42
|
-
<Renderer
|
|
43
|
-
content={item.question.content}
|
|
44
|
-
images={item.question.images}
|
|
45
|
-
widgets={item.question.widgets}
|
|
46
|
-
problemNum={0}
|
|
47
|
-
apiOptions={apiOptions}
|
|
48
|
-
/>
|
|
49
|
-
</Dependencies.DependenciesContext.Provider>
|
|
50
|
-
</RenderStateRoot>,
|
|
51
|
-
{includeDefaultTestHarness: false},
|
|
52
|
-
);
|
|
53
|
-
return wrapper.childAt(0).instance();
|
|
54
|
-
};
|
|
55
|
-
|
|
56
32
|
describe("Perseus API", function () {
|
|
57
33
|
beforeEach(() => {
|
|
58
34
|
jest.spyOn(Dependencies, "getDependencies").mockReturnValue(
|
|
@@ -63,34 +39,40 @@ describe("Perseus API", function () {
|
|
|
63
39
|
|
|
64
40
|
describe("setInputValue", function () {
|
|
65
41
|
it("should be able to produce a correctly graded value", function () {
|
|
66
|
-
|
|
67
|
-
renderer
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
42
|
+
// Arrange
|
|
43
|
+
const {renderer} = renderQuestion(inputNumber1Item.question);
|
|
44
|
+
|
|
45
|
+
// Act
|
|
46
|
+
renderer.setInputValue(["input-number 1"], "5", () => undefined);
|
|
47
|
+
|
|
48
|
+
// Assert
|
|
49
|
+
expect(renderer).toHaveBeenAnsweredCorrectly();
|
|
71
50
|
});
|
|
72
51
|
|
|
73
52
|
it("should be able to produce a wrong value", function () {
|
|
74
|
-
|
|
75
|
-
renderer
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
53
|
+
// Arrange
|
|
54
|
+
const {renderer} = renderQuestion(inputNumber1Item.question);
|
|
55
|
+
|
|
56
|
+
// Act
|
|
57
|
+
renderer.setInputValue(["input-number 1"], "3");
|
|
58
|
+
|
|
59
|
+
// Assert
|
|
60
|
+
expect(renderer).toHaveBeenAnsweredIncorrectly();
|
|
79
61
|
});
|
|
80
62
|
|
|
81
63
|
it("should be able to produce an empty score", function () {
|
|
82
|
-
|
|
64
|
+
// Arrange
|
|
65
|
+
const {renderer} = renderQuestion(inputNumber1Item.question);
|
|
66
|
+
|
|
83
67
|
renderer.setInputValue(["input-number 1"], "3");
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
expect(score.earned).toBe(0);
|
|
68
|
+
expect(renderer).toHaveBeenAnsweredIncorrectly();
|
|
69
|
+
|
|
87
70
|
renderer.setInputValue(["input-number 1"], "");
|
|
88
|
-
|
|
89
|
-
expect(score.type).toBe("invalid");
|
|
71
|
+
expect(renderer).toHaveInvalidInput();
|
|
90
72
|
});
|
|
91
73
|
|
|
92
74
|
it("should be able to accept a callback", function (done) {
|
|
93
|
-
const renderer =
|
|
75
|
+
const {renderer} = renderQuestion(inputNumber1Item.question);
|
|
94
76
|
renderer.setInputValue(["input-number 1"], "3", function () {
|
|
95
77
|
const guess = renderer.getUserInput()[0];
|
|
96
78
|
expect(guess.currentValue).toBe("3");
|
|
@@ -102,13 +84,13 @@ describe("Perseus API", function () {
|
|
|
102
84
|
|
|
103
85
|
describe("getInputPaths", function () {
|
|
104
86
|
it("should be able to find all the input widgets", function () {
|
|
105
|
-
const renderer =
|
|
87
|
+
const {renderer} = renderQuestion(inputNumber2Item.question);
|
|
106
88
|
const numPaths = renderer.getInputPaths().length;
|
|
107
89
|
expect(numPaths).toBe(2);
|
|
108
90
|
});
|
|
109
91
|
|
|
110
92
|
it("should be able to find all inputs within widgets", function () {
|
|
111
|
-
const renderer =
|
|
93
|
+
const {renderer} = renderQuestion(tableItem.question);
|
|
112
94
|
const numPaths = renderer.getInputPaths().length;
|
|
113
95
|
expect(numPaths).toBe(8);
|
|
114
96
|
});
|
|
@@ -116,7 +98,7 @@ describe("Perseus API", function () {
|
|
|
116
98
|
|
|
117
99
|
describe("getDOMNodeForPath", function () {
|
|
118
100
|
it("should find one DOM node per <input>", function () {
|
|
119
|
-
const renderer =
|
|
101
|
+
const {renderer} = renderQuestion(inputNumber2Item.question);
|
|
120
102
|
const inputPaths = renderer.getInputPaths();
|
|
121
103
|
const allInputs = TestUtils.scryRenderedDOMComponentsWithTag(
|
|
122
104
|
renderer,
|
|
@@ -126,13 +108,14 @@ describe("Perseus API", function () {
|
|
|
126
108
|
});
|
|
127
109
|
|
|
128
110
|
it("should find the right DOM nodes for the <input>s", function () {
|
|
129
|
-
const renderer =
|
|
111
|
+
const {renderer} = renderQuestion(inputNumber2Item.question);
|
|
130
112
|
const inputPaths = renderer.getInputPaths();
|
|
131
113
|
const allInputs = TestUtils.scryRenderedDOMComponentsWithTag(
|
|
132
114
|
renderer,
|
|
133
115
|
"input",
|
|
134
116
|
);
|
|
135
117
|
_.each(inputPaths, (inputPath, i) => {
|
|
118
|
+
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
136
119
|
const $node = $(renderer.getDOMNodeForPath(inputPath));
|
|
137
120
|
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
138
121
|
const $input = $(ReactDOM.findDOMNode(allInputs[i]));
|
|
@@ -145,13 +128,13 @@ describe("Perseus API", function () {
|
|
|
145
128
|
describe("onInputError", function () {
|
|
146
129
|
it("should call a callback when grading an empty input-number", function () {
|
|
147
130
|
let wasCalled;
|
|
148
|
-
const renderer =
|
|
131
|
+
const {renderer} = renderQuestion(inputNumber1Item.question, {
|
|
149
132
|
onInputError: function (widgetId) {
|
|
150
133
|
wasCalled = true;
|
|
151
134
|
},
|
|
152
135
|
});
|
|
153
|
-
|
|
154
|
-
expect(
|
|
136
|
+
|
|
137
|
+
expect(renderer).toHaveInvalidInput();
|
|
155
138
|
expect(wasCalled).toBe(true);
|
|
156
139
|
});
|
|
157
140
|
});
|
|
@@ -164,7 +147,7 @@ describe("Perseus API", function () {
|
|
|
164
147
|
// version
|
|
165
148
|
expect(ClassNames.FOCUSED).toBe("perseus-focused");
|
|
166
149
|
|
|
167
|
-
const renderer =
|
|
150
|
+
const {renderer} = renderQuestion(inputNumber1Item.question);
|
|
168
151
|
|
|
169
152
|
const input =
|
|
170
153
|
// @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2339 - Property 'querySelector' does not exist on type 'Element | Text'.
|
|
@@ -191,7 +174,7 @@ describe("Perseus API", function () {
|
|
|
191
174
|
let callCount = 0;
|
|
192
175
|
let newFocusResult;
|
|
193
176
|
let oldFocusResult;
|
|
194
|
-
const renderer =
|
|
177
|
+
const {renderer} = renderQuestion(inputNumber1Item.question, {
|
|
195
178
|
onFocusChange: function (newFocus, oldFocus) {
|
|
196
179
|
callCount++;
|
|
197
180
|
newFocusResult = newFocus;
|
|
@@ -235,7 +218,7 @@ describe("Perseus API", function () {
|
|
|
235
218
|
let callCount = 0;
|
|
236
219
|
let newFocusResult;
|
|
237
220
|
let oldFocusResult;
|
|
238
|
-
const renderer =
|
|
221
|
+
const {renderer} = renderQuestion(inputNumber2Item.question, {
|
|
239
222
|
onFocusChange: function (newFocus, oldFocus) {
|
|
240
223
|
callCount++;
|
|
241
224
|
newFocusResult = newFocus;
|
package/src/components/graph.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import * as React from "react";
|
|
|
5
5
|
import ReactDOM from "react-dom";
|
|
6
6
|
import _ from "underscore";
|
|
7
7
|
|
|
8
|
+
import AssetContext from "../asset-context";
|
|
8
9
|
import {interactiveSizes} from "../styles/constants";
|
|
9
10
|
import Util from "../util";
|
|
10
11
|
import GraphUtils from "../util/graph-utils";
|
|
@@ -395,14 +396,19 @@ class Graph extends React.Component<Props> {
|
|
|
395
396
|
if (imageData.url) {
|
|
396
397
|
const scale = this.props.box[0] / interactiveSizes.defaultBoxSize;
|
|
397
398
|
image = (
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
399
|
+
<AssetContext.Consumer>
|
|
400
|
+
{({setAssetStatus}) => (
|
|
401
|
+
<SvgImage
|
|
402
|
+
// @ts-expect-error - TS2741 - Property 'alt' is missing in type '{ src: any; width: any; height: any; scale: number; responsive: false; }' but required in type 'Pick<Readonly<Props> & Readonly<{ children?: ReactNode; }>, "children" | "height" | "width" | "title" | "alt" | "trackInteraction" | "preloader" | "allowFullBleed" | "extraGraphie" | "overrideAriaHidden">'.
|
|
403
|
+
src={imageData.url}
|
|
404
|
+
width={imageData.width}
|
|
405
|
+
height={imageData.height}
|
|
406
|
+
scale={scale}
|
|
407
|
+
responsive={false}
|
|
408
|
+
setAssetStatus={setAssetStatus}
|
|
409
|
+
/>
|
|
410
|
+
)}
|
|
411
|
+
</AssetContext.Consumer>
|
|
406
412
|
);
|
|
407
413
|
} else {
|
|
408
414
|
image = null;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
2
|
-
/* eslint-disable
|
|
2
|
+
/* eslint-disable react/no-unsafe */
|
|
3
3
|
import {CircularSpinner} from "@khanacademy/wonder-blocks-progress-spinner";
|
|
4
4
|
import classNames from "classnames";
|
|
5
5
|
import $ from "jquery";
|
|
@@ -79,7 +79,7 @@ const doJSONP = function (url: string, options) {
|
|
|
79
79
|
// system (with file://). We replace urls that start with `web+graphie`
|
|
80
80
|
// in the perseus json with this `file+graphie` prefix to indicate that
|
|
81
81
|
// they should have the `file://` protocol instead of `https://`.
|
|
82
|
-
const svgLocalLabelsRegex = /^file\+graphie
|
|
82
|
+
const svgLocalLabelsRegex = /^file\+graphie:/;
|
|
83
83
|
const hashRegex = /\/([^/]+)$/;
|
|
84
84
|
|
|
85
85
|
function isImageProbablyPhotograph(imageUrl) {
|
|
@@ -180,6 +180,16 @@ type Props = {
|
|
|
180
180
|
setAssetStatus: (assetKey: string, loaded: boolean) => void;
|
|
181
181
|
};
|
|
182
182
|
|
|
183
|
+
type DefaultProps = {
|
|
184
|
+
constrainHeight: NonNullable<Props["constrainHeight"]>;
|
|
185
|
+
onUpdate: NonNullable<Props["onUpdate"]>;
|
|
186
|
+
responsive: NonNullable<Props["responsive"]>;
|
|
187
|
+
scale: NonNullable<Props["scale"]>;
|
|
188
|
+
setAssetStatus: NonNullable<Props["setAssetStatus"]>;
|
|
189
|
+
src: NonNullable<Props["src"]>;
|
|
190
|
+
zoomToFullSizeOnMobile: NonNullable<Props["zoomToFullSizeOnMobile"]>;
|
|
191
|
+
};
|
|
192
|
+
|
|
183
193
|
type Label = {
|
|
184
194
|
coordinates: ReadonlyArray<any>;
|
|
185
195
|
content: string;
|
|
@@ -212,15 +222,7 @@ type State = {
|
|
|
212
222
|
class SvgImage extends React.Component<Props, State> {
|
|
213
223
|
_isMounted: boolean;
|
|
214
224
|
|
|
215
|
-
static defaultProps: {
|
|
216
|
-
constrainHeight: boolean;
|
|
217
|
-
onUpdate: () => void;
|
|
218
|
-
responsive: boolean;
|
|
219
|
-
scale: number;
|
|
220
|
-
setAssetStatus: (src: string, status: boolean) => void;
|
|
221
|
-
src: string;
|
|
222
|
-
zoomToFullSizeOnMobile: boolean;
|
|
223
|
-
} = {
|
|
225
|
+
static defaultProps: DefaultProps = {
|
|
224
226
|
constrainHeight: false,
|
|
225
227
|
onUpdate: () => {},
|
|
226
228
|
responsive: true,
|
|
@@ -102,7 +102,6 @@ const ProvideKeypad = {
|
|
|
102
102
|
}}
|
|
103
103
|
// @ts-expect-error - TS2339 - Property 'props' does not exist on type '{ readonly propTypes: { readonly apiOptions: React.PropType<{ customKeypad?: boolean | undefined; nativeKeypadProxy?: ((...a: readonly any[]) => unknown) | undefined; }>; readonly keypadStyle: Requireable<any>; }; readonly getInitialState: () => { ...; }; readonly componentDidMount: () => void; readonly componentWil...'.
|
|
104
104
|
style={_this.props.keypadStyle}
|
|
105
|
-
useV2Keypad={apiOptions.useV2Keypad}
|
|
106
105
|
onAnalyticsEvent={async () => {
|
|
107
106
|
// Intentionally left empty. This component is
|
|
108
107
|
// deprecated and so we don't want/need to instrument
|