@khanacademy/perseus 11.5.0 → 11.6.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 +26 -0
- package/dist/es/index.css +6 -6
- package/dist/es/index.js +17 -5
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +6 -6
- package/dist/index.js +17 -5
- package/dist/index.js.map +1 -1
- package/dist/widgets/numeric-input.d.ts +1 -1
- package/package.json +3 -2
- package/src/__tests__/__snapshots__/server-item-renderer.test.tsx.snap +3 -3
- package/src/__tests__/item-renderer.test.tsx +24 -18
- package/src/hint-renderer.tsx +1 -0
- package/src/multi-items/__tests__/__snapshots__/multi-renderer.test.tsx.snap +1 -1
- package/src/perseus-types.ts +2 -1
- package/src/styles/khan-exercise.css +6 -4
- package/src/styles/perseus-renderer.less +0 -1
- package/src/widgets/__stories__/test-keypad-context-wrapper.tsx +7 -21
- package/src/widgets/__tests__/expression-mobile.test.tsx +333 -0
- package/src/widgets/__tests__/numeric-input.test.ts +35 -0
- package/src/widgets/numeric-input.tsx +22 -10
- package/tsconfig-build.tsbuildinfo +1 -1
|
@@ -36,7 +36,7 @@ export declare class NumericInput extends React.Component<Props, State> {
|
|
|
36
36
|
static defaultProps: DefaultProps;
|
|
37
37
|
static getUserInputFromProps(props: Props): UserInput;
|
|
38
38
|
static getOneCorrectAnswerFromRubric(rubric: Rubric): string | null | undefined;
|
|
39
|
-
static validate(
|
|
39
|
+
static validate(userInput: UserInput, rubric: Rubric): PerseusScore;
|
|
40
40
|
state: State;
|
|
41
41
|
examples: () => ReadonlyArray<string>;
|
|
42
42
|
shouldShowExamples: () => 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": "11.
|
|
6
|
+
"version": "11.6.0",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@khanacademy/kas": "^0.3.1",
|
|
26
26
|
"@khanacademy/kmath": "^0.1.2",
|
|
27
|
-
"@khanacademy/math-input": "^
|
|
27
|
+
"@khanacademy/math-input": "^14.0.0",
|
|
28
28
|
"@khanacademy/perseus-core": "1.1.0",
|
|
29
29
|
"@khanacademy/perseus-linter": "^0.3.4",
|
|
30
30
|
"@khanacademy/pure-markdown": "^0.2.6",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"jquery": "^2.1.1",
|
|
58
58
|
"katex": "^0.11.1",
|
|
59
59
|
"lodash.debounce": "^4.0.8",
|
|
60
|
+
"mathquill": "git+https://git@github.com/Khan/mathquill.git#32d9f351aaa68537170b3120a52e99b8def3a2c3",
|
|
60
61
|
"perseus-build-settings": "^0.2.0",
|
|
61
62
|
"prop-types": "^15.6.1",
|
|
62
63
|
"react": "16.14.0",
|
|
@@ -189,7 +189,7 @@ exports[`server item renderer should snapshot: initial render 1`] = `
|
|
|
189
189
|
class=""
|
|
190
190
|
>
|
|
191
191
|
<div
|
|
192
|
-
class="perseus-hint-renderer"
|
|
192
|
+
class="hint perseus-hint-renderer"
|
|
193
193
|
tabindex="-1"
|
|
194
194
|
>
|
|
195
195
|
<span
|
|
@@ -219,7 +219,7 @@ exports[`server item renderer should snapshot: initial render 1`] = `
|
|
|
219
219
|
</div>
|
|
220
220
|
</div>
|
|
221
221
|
<div
|
|
222
|
-
class="perseus-hint-renderer"
|
|
222
|
+
class="hint perseus-hint-renderer"
|
|
223
223
|
tabindex="-1"
|
|
224
224
|
>
|
|
225
225
|
<span
|
|
@@ -249,7 +249,7 @@ exports[`server item renderer should snapshot: initial render 1`] = `
|
|
|
249
249
|
</div>
|
|
250
250
|
</div>
|
|
251
251
|
<div
|
|
252
|
-
class="perseus-hint-renderer last-hint last-rendered"
|
|
252
|
+
class="hint perseus-hint-renderer last-hint last-rendered"
|
|
253
253
|
tabindex="-1"
|
|
254
254
|
>
|
|
255
255
|
<span
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import {StatefulKeypadContextProvider} from "@khanacademy/math-input";
|
|
1
2
|
import {RenderStateRoot} from "@khanacademy/wonder-blocks-core";
|
|
2
|
-
import {fireEvent, render, screen} from "@testing-library/react";
|
|
3
|
+
import {fireEvent, render, screen, waitFor} from "@testing-library/react";
|
|
3
4
|
import userEvent from "@testing-library/user-event";
|
|
4
5
|
import * as React from "react";
|
|
5
6
|
import "@testing-library/jest-dom"; // Imports custom matchers
|
|
@@ -71,23 +72,25 @@ export const renderQuestion = (
|
|
|
71
72
|
let renderer: ItemRenderer | null = null;
|
|
72
73
|
const {container} = render(
|
|
73
74
|
<RenderStateRoot>
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
75
|
+
<StatefulKeypadContextProvider>
|
|
76
|
+
<ItemRenderer
|
|
77
|
+
ref={(node) => (renderer = node)}
|
|
78
|
+
apiOptions={apiOptions}
|
|
79
|
+
item={question}
|
|
80
|
+
problemNum={0}
|
|
81
|
+
reviewMode={false}
|
|
82
|
+
savedState=""
|
|
83
|
+
controlPeripherals={false}
|
|
84
|
+
dependencies={testDependenciesV2}
|
|
85
|
+
{...optionalProps}
|
|
86
|
+
/>
|
|
87
|
+
{/* The ItemRenderer _requires_ two divs: a work area and hints
|
|
86
88
|
area. Without both of these, it fails to render anything! */}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
<div id="workarea" />
|
|
90
|
+
<div id="hintsarea" />
|
|
89
91
|
|
|
90
|
-
|
|
92
|
+
<Peripherals />
|
|
93
|
+
</StatefulKeypadContextProvider>
|
|
91
94
|
</RenderStateRoot>,
|
|
92
95
|
);
|
|
93
96
|
if (!renderer) {
|
|
@@ -309,18 +312,21 @@ describe("item renderer", () => {
|
|
|
309
312
|
);
|
|
310
313
|
});
|
|
311
314
|
|
|
312
|
-
it("should activate the keypad when widget with input is focused", () => {
|
|
315
|
+
it("should activate the keypad when widget with input is focused", async () => {
|
|
313
316
|
// Arrange
|
|
314
317
|
const {renderer} = renderQuestion(itemWithInput, {
|
|
315
318
|
isMobile: true,
|
|
316
319
|
customKeypad: true,
|
|
320
|
+
useV2Keypad: true,
|
|
317
321
|
});
|
|
318
322
|
|
|
319
323
|
// Act
|
|
320
324
|
renderer.focus();
|
|
321
325
|
|
|
322
326
|
// Assert
|
|
323
|
-
|
|
327
|
+
await waitFor(() => {
|
|
328
|
+
expect(screen.getByLabelText("7")).toBeVisible();
|
|
329
|
+
});
|
|
324
330
|
});
|
|
325
331
|
|
|
326
332
|
it("should provide current and previous focus paths on focus change to, and away from, a single widget", () => {
|
package/src/hint-renderer.tsx
CHANGED
|
@@ -62,6 +62,7 @@ class HintRenderer extends React.Component<Props> {
|
|
|
62
62
|
const {isMobile} = apiOptions;
|
|
63
63
|
|
|
64
64
|
const classNames = classnames(
|
|
65
|
+
"hint",
|
|
65
66
|
!isMobile && "perseus-hint-renderer",
|
|
66
67
|
isMobile && css(styles.newHint),
|
|
67
68
|
isMobile && lastRendered && css(styles.lastRenderedNewHint),
|
package/src/perseus-types.ts
CHANGED
|
@@ -762,7 +762,8 @@ export type PerseusNumericInputAnswer = {
|
|
|
762
762
|
// The forms available for this answer. Options: "integer, ""decimal", "proper", "improper", "mixed", or "pi"
|
|
763
763
|
// NOTE: perseus_data.go says this is required even though it isn't necessary.
|
|
764
764
|
answerForms?: ReadonlyArray<MathFormat>;
|
|
765
|
-
// Whether the answerForms
|
|
765
|
+
// Whether we should check the answer strictly against the the configured answerForms (strict = true)
|
|
766
|
+
// or include the set of default answerForms (strict = false).
|
|
766
767
|
strict: boolean;
|
|
767
768
|
// A range of error +/- the value
|
|
768
769
|
// NOTE: perseus_data.go says this is non-nullable even though we handle null values.
|
|
@@ -10,6 +10,12 @@ var {
|
|
|
10
10
|
font-style: normal;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
.hint.last-hint {
|
|
14
|
+
.paragraph {
|
|
15
|
+
font-weight: bold;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
.hint_blue {
|
|
14
20
|
color: #6495ed;
|
|
15
21
|
}
|
|
@@ -32,10 +38,6 @@ var {
|
|
|
32
38
|
color: #9d38bd;
|
|
33
39
|
}
|
|
34
40
|
|
|
35
|
-
.last-hint {
|
|
36
|
-
font-weight: bold;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
41
|
div.subhint {
|
|
40
42
|
border: 1px solid #aaaaaa;
|
|
41
43
|
background: #f9f9f9;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
KeypadContext,
|
|
3
|
+
StatefulKeypadContextProvider,
|
|
4
|
+
MobileKeypad,
|
|
5
|
+
} from "@khanacademy/math-input";
|
|
2
6
|
import {View} from "@khanacademy/wonder-blocks-core";
|
|
3
7
|
import {action} from "@storybook/addon-actions";
|
|
4
8
|
import {StyleSheet} from "aphrodite";
|
|
@@ -29,29 +33,11 @@ type Props = {
|
|
|
29
33
|
};
|
|
30
34
|
|
|
31
35
|
const TestKeypadContextWrapper = (props: Props): React.ReactElement => {
|
|
32
|
-
const [keypadElement, setKeypadElement] = React.useState(null);
|
|
33
|
-
const [renderer, setRenderer] = React.useState(null);
|
|
34
|
-
const [scrollableElement, setScrollableElement] = React.useState(
|
|
35
|
-
document.body,
|
|
36
|
-
);
|
|
37
|
-
|
|
38
36
|
return (
|
|
39
|
-
<
|
|
40
|
-
value={{
|
|
41
|
-
// @ts-expect-error - TS2322 - Type 'Dispatch<SetStateAction<HTMLElement>>' is not assignable to type '(scrollableElement?: HTMLElement | null | undefined) => void'.
|
|
42
|
-
setKeypadElement,
|
|
43
|
-
keypadElement,
|
|
44
|
-
// @ts-expect-error - TS2322 - Type 'Dispatch<SetStateAction<HTMLElement>>' is not assignable to type '(scrollableElement?: HTMLElement | null | undefined) => void'.
|
|
45
|
-
setRenderer,
|
|
46
|
-
renderer,
|
|
47
|
-
// @ts-expect-error - TS2322 - Type 'Dispatch<SetStateAction<HTMLElement>>' is not assignable to type '(scrollableElement?: HTMLElement | null | undefined) => void'.
|
|
48
|
-
setScrollableElement,
|
|
49
|
-
scrollableElement,
|
|
50
|
-
}}
|
|
51
|
-
>
|
|
37
|
+
<StatefulKeypadContextProvider>
|
|
52
38
|
{props.children}
|
|
53
39
|
<Footer />
|
|
54
|
-
</
|
|
40
|
+
</StatefulKeypadContextProvider>
|
|
55
41
|
);
|
|
56
42
|
};
|
|
57
43
|
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import {
|
|
3
|
+
KeypadContext,
|
|
4
|
+
StatefulKeypadContextProvider,
|
|
5
|
+
MobileKeypad,
|
|
6
|
+
} from "@khanacademy/math-input";
|
|
7
|
+
import {RenderStateRoot} from "@khanacademy/wonder-blocks-core";
|
|
8
|
+
import {
|
|
9
|
+
fireEvent,
|
|
10
|
+
render,
|
|
11
|
+
screen,
|
|
12
|
+
waitFor,
|
|
13
|
+
within,
|
|
14
|
+
} from "@testing-library/react";
|
|
15
|
+
import userEvent from "@testing-library/user-event";
|
|
16
|
+
import MathQuill from "mathquill";
|
|
17
|
+
import * as React from "react";
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
testDependencies,
|
|
21
|
+
testDependenciesV2,
|
|
22
|
+
} from "../../../../../testing/test-dependencies";
|
|
23
|
+
import * as Dependencies from "../../dependencies";
|
|
24
|
+
import WrappedServerItemRenderer from "../../server-item-renderer";
|
|
25
|
+
import {registerWidget} from "../../widgets";
|
|
26
|
+
import {expressionItem2} from "../__testdata__/expression.testdata";
|
|
27
|
+
import ExpressionExport from "../expression";
|
|
28
|
+
|
|
29
|
+
const MQ = MathQuill.getInterface(2);
|
|
30
|
+
|
|
31
|
+
function RendererWithContext({item}) {
|
|
32
|
+
return (
|
|
33
|
+
<KeypadContext.Consumer>
|
|
34
|
+
{({keypadElement}) => {
|
|
35
|
+
return (
|
|
36
|
+
<WrappedServerItemRenderer
|
|
37
|
+
apiOptions={{
|
|
38
|
+
isMobile: true,
|
|
39
|
+
customKeypad: true,
|
|
40
|
+
useV2Keypad: true,
|
|
41
|
+
}}
|
|
42
|
+
item={item}
|
|
43
|
+
problemNum={0}
|
|
44
|
+
reviewMode={false}
|
|
45
|
+
dependencies={testDependenciesV2}
|
|
46
|
+
keypadElement={keypadElement}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}}
|
|
50
|
+
</KeypadContext.Consumer>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function KeypadWithContext() {
|
|
55
|
+
return (
|
|
56
|
+
<KeypadContext.Consumer>
|
|
57
|
+
{({setKeypadElement}) => {
|
|
58
|
+
return (
|
|
59
|
+
<MobileKeypad
|
|
60
|
+
onElementMounted={setKeypadElement}
|
|
61
|
+
onDismiss={() => {}}
|
|
62
|
+
onAnalyticsEvent={async () => {}}
|
|
63
|
+
useV2Keypad
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}}
|
|
67
|
+
</KeypadContext.Consumer>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function ConnectedRenderer({item = expressionItem2}) {
|
|
72
|
+
return (
|
|
73
|
+
<RenderStateRoot>
|
|
74
|
+
<StatefulKeypadContextProvider>
|
|
75
|
+
<RendererWithContext item={item} />
|
|
76
|
+
<KeypadWithContext />
|
|
77
|
+
</StatefulKeypadContextProvider>
|
|
78
|
+
</RenderStateRoot>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
describe("expression mobile", () => {
|
|
83
|
+
beforeAll(() => {
|
|
84
|
+
registerWidget("expression", ExpressionExport);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
jest.spyOn(Dependencies, "getDependencies").mockReturnValue(
|
|
89
|
+
testDependencies,
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
afterEach(() => {
|
|
94
|
+
// The Renderer uses a timer to wait for widgets to complete rendering.
|
|
95
|
+
// If we don't spin the timers here, then the timer fires in the test
|
|
96
|
+
// _after_ and breaks it because we do setState() in the callback,
|
|
97
|
+
// and by that point the component has been unmounted.
|
|
98
|
+
jest.runOnlyPendingTimers();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("renders input", () => {
|
|
102
|
+
render(<ConnectedRenderer />);
|
|
103
|
+
|
|
104
|
+
expect(
|
|
105
|
+
screen.getByLabelText(
|
|
106
|
+
"Math input box Tap with one or two fingers to open keyboard",
|
|
107
|
+
),
|
|
108
|
+
).toBeInTheDocument();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("shows the keypad after input interaction", async () => {
|
|
112
|
+
render(<ConnectedRenderer />);
|
|
113
|
+
|
|
114
|
+
const input = screen.getByLabelText(
|
|
115
|
+
"Math input box Tap with one or two fingers to open keyboard",
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
fireEvent.touchStart(input);
|
|
119
|
+
|
|
120
|
+
// waiting because `visibility` is animated
|
|
121
|
+
await waitFor(() =>
|
|
122
|
+
expect(screen.getByRole("button", {name: "1"})).toBeVisible(),
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("is possible to use the keypad", async () => {
|
|
127
|
+
render(<ConnectedRenderer />);
|
|
128
|
+
|
|
129
|
+
const input = screen.getByLabelText(
|
|
130
|
+
"Math input box Tap with one or two fingers to open keyboard",
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
fireEvent.touchStart(input);
|
|
134
|
+
|
|
135
|
+
// waiting because `visibility` is animated
|
|
136
|
+
await waitFor(() =>
|
|
137
|
+
expect(screen.getByRole("button", {name: "1"})).toBeVisible(),
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
userEvent.click(screen.getByRole("button", {name: "1"}));
|
|
141
|
+
|
|
142
|
+
// MathQuill is problematic,
|
|
143
|
+
// this is the only way I know how to test the "input"
|
|
144
|
+
const mathquillInput =
|
|
145
|
+
// eslint-disable-next-line testing-library/no-node-access
|
|
146
|
+
document.getElementsByClassName("mq-root-block")[0] as HTMLElement;
|
|
147
|
+
const span1 = within(mathquillInput).getByText("1");
|
|
148
|
+
|
|
149
|
+
expect(span1).toBeVisible();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("is possible to type many numbers", async () => {
|
|
153
|
+
render(<ConnectedRenderer />);
|
|
154
|
+
|
|
155
|
+
const input = screen.getByLabelText(
|
|
156
|
+
"Math input box Tap with one or two fingers to open keyboard",
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
fireEvent.touchStart(input);
|
|
160
|
+
|
|
161
|
+
// waiting because `visibility` is animated
|
|
162
|
+
await waitFor(() =>
|
|
163
|
+
expect(screen.getByRole("button", {name: "1"})).toBeVisible(),
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const testNumbers = [8, 6, 7, 5, 3, 0, 9];
|
|
167
|
+
testNumbers.forEach((num) => {
|
|
168
|
+
userEvent.click(screen.getByRole("button", {name: `${num}`}));
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// MathQuill is problematic,
|
|
172
|
+
// this is the only way I know how to test the "input"
|
|
173
|
+
const mathquillInput =
|
|
174
|
+
// eslint-disable-next-line testing-library/no-node-access
|
|
175
|
+
document.getElementsByClassName("mq-root-block")[0] as HTMLElement;
|
|
176
|
+
|
|
177
|
+
testNumbers.forEach((num) => {
|
|
178
|
+
const span = within(mathquillInput).getByText(`${num}`);
|
|
179
|
+
|
|
180
|
+
expect(span).toBeVisible();
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("can handle symbols", async () => {
|
|
185
|
+
render(<ConnectedRenderer />);
|
|
186
|
+
|
|
187
|
+
const input = screen.getByLabelText(
|
|
188
|
+
"Math input box Tap with one or two fingers to open keyboard",
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
fireEvent.touchStart(input);
|
|
192
|
+
|
|
193
|
+
// waiting because `visibility` is animated
|
|
194
|
+
await waitFor(() =>
|
|
195
|
+
expect(screen.getByRole("button", {name: "1"})).toBeVisible(),
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
userEvent.click(screen.getByRole("button", {name: "4"}));
|
|
199
|
+
userEvent.click(screen.getByRole("button", {name: "2"}));
|
|
200
|
+
userEvent.click(screen.getByRole("button", {name: "Percent"}));
|
|
201
|
+
|
|
202
|
+
// MathQuill is problematic,
|
|
203
|
+
// this is the only way I know how to test the "input"
|
|
204
|
+
const mathquillInput =
|
|
205
|
+
// eslint-disable-next-line testing-library/no-node-access
|
|
206
|
+
document.getElementsByClassName("mq-root-block")[0] as HTMLElement;
|
|
207
|
+
const span4 = within(mathquillInput).getByText("4");
|
|
208
|
+
const span2 = within(mathquillInput).getByText("2");
|
|
209
|
+
const spanPercent = within(mathquillInput).getByText("%");
|
|
210
|
+
|
|
211
|
+
expect(span4).toBeVisible();
|
|
212
|
+
expect(span2).toBeVisible();
|
|
213
|
+
expect(spanPercent).toBeVisible();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("can do arithmetic", async () => {
|
|
217
|
+
render(<ConnectedRenderer />);
|
|
218
|
+
|
|
219
|
+
const input = screen.getByLabelText(
|
|
220
|
+
"Math input box Tap with one or two fingers to open keyboard",
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
fireEvent.touchStart(input);
|
|
224
|
+
|
|
225
|
+
// waiting because `visibility` is animated
|
|
226
|
+
await waitFor(() =>
|
|
227
|
+
expect(screen.getByRole("button", {name: "1"})).toBeVisible(),
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
userEvent.click(screen.getByRole("button", {name: "4"}));
|
|
231
|
+
userEvent.click(screen.getByRole("button", {name: "Plus"}));
|
|
232
|
+
userEvent.click(screen.getByRole("button", {name: "2"}));
|
|
233
|
+
|
|
234
|
+
// MathQuill is problematic,
|
|
235
|
+
// this is the only way I know how to test the "input"
|
|
236
|
+
const mathquillInput =
|
|
237
|
+
// eslint-disable-next-line testing-library/no-node-access
|
|
238
|
+
document.getElementsByClassName("mq-root-block")[0] as HTMLElement;
|
|
239
|
+
const span4 = within(mathquillInput).getByText("4");
|
|
240
|
+
const spanPlus = within(mathquillInput).getByText("+");
|
|
241
|
+
const span2 = within(mathquillInput).getByText("2");
|
|
242
|
+
|
|
243
|
+
expect(span4).toBeVisible();
|
|
244
|
+
expect(spanPlus).toBeVisible();
|
|
245
|
+
expect(span2).toBeVisible();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("can navigate to operators page", async () => {
|
|
249
|
+
render(<ConnectedRenderer />);
|
|
250
|
+
|
|
251
|
+
const input = screen.getByLabelText(
|
|
252
|
+
"Math input box Tap with one or two fingers to open keyboard",
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
fireEvent.touchStart(input);
|
|
256
|
+
|
|
257
|
+
// waiting because `visibility` is animated
|
|
258
|
+
await waitFor(() =>
|
|
259
|
+
expect(screen.getByRole("button", {name: "1"})).toBeVisible(),
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
userEvent.click(screen.getByRole("tab", {name: "Operators"}));
|
|
263
|
+
expect(screen.getByRole("button", {name: "Equals sign"})).toBeVisible();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("can navigate to geometry page", async () => {
|
|
267
|
+
render(<ConnectedRenderer />);
|
|
268
|
+
|
|
269
|
+
const input = screen.getByLabelText(
|
|
270
|
+
"Math input box Tap with one or two fingers to open keyboard",
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
fireEvent.touchStart(input);
|
|
274
|
+
|
|
275
|
+
// waiting because `visibility` is animated
|
|
276
|
+
await waitFor(() =>
|
|
277
|
+
expect(screen.getByRole("button", {name: "1"})).toBeVisible(),
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
userEvent.click(screen.getByRole("tab", {name: "Geometry"}));
|
|
281
|
+
expect(screen.getByRole("button", {name: "Sine"})).toBeVisible();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("can navigate to extras page", async () => {
|
|
285
|
+
render(<ConnectedRenderer />);
|
|
286
|
+
|
|
287
|
+
const input = screen.getByLabelText(
|
|
288
|
+
"Math input box Tap with one or two fingers to open keyboard",
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
fireEvent.touchStart(input);
|
|
292
|
+
|
|
293
|
+
// waiting because `visibility` is animated
|
|
294
|
+
await waitFor(() =>
|
|
295
|
+
expect(screen.getByRole("button", {name: "1"})).toBeVisible(),
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
userEvent.click(screen.getByRole("tab", {name: "Extras"}));
|
|
299
|
+
expect(screen.getByRole("button", {name: "x"})).toBeVisible();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it("can write a full equation", async () => {
|
|
303
|
+
render(<ConnectedRenderer />);
|
|
304
|
+
|
|
305
|
+
const input = screen.getByLabelText(
|
|
306
|
+
"Math input box Tap with one or two fingers to open keyboard",
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
fireEvent.touchStart(input);
|
|
310
|
+
|
|
311
|
+
// waiting because `visibility` is animated
|
|
312
|
+
await waitFor(() =>
|
|
313
|
+
expect(screen.getByRole("button", {name: "1"})).toBeVisible(),
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
userEvent.click(screen.getByRole("tab", {name: "Extras"}));
|
|
317
|
+
userEvent.click(screen.getByRole("button", {name: "x"}));
|
|
318
|
+
userEvent.click(screen.getByRole("tab", {name: "Operators"}));
|
|
319
|
+
userEvent.click(screen.getByRole("button", {name: "Equals sign"}));
|
|
320
|
+
userEvent.click(screen.getByRole("tab", {name: "Geometry"}));
|
|
321
|
+
userEvent.click(screen.getByRole("button", {name: "Sine"}));
|
|
322
|
+
userEvent.click(screen.getByRole("tab", {name: "Numbers"}));
|
|
323
|
+
userEvent.click(screen.getByRole("button", {name: "9"}));
|
|
324
|
+
|
|
325
|
+
// MathQuill is problematic,
|
|
326
|
+
// this is how to get the value of the input directly from MQ
|
|
327
|
+
const mathquillInstance =
|
|
328
|
+
// eslint-disable-next-line testing-library/no-node-access
|
|
329
|
+
MQ(document.getElementsByClassName("mq-editable-field")[0]);
|
|
330
|
+
|
|
331
|
+
expect(mathquillInstance.latex()).toBe("x=\\sin\\left(9\\right)");
|
|
332
|
+
});
|
|
333
|
+
});
|
|
@@ -250,6 +250,41 @@ describe("static function validate", () => {
|
|
|
250
250
|
expect(score.message?.includes("pi")).toBeFalsy();
|
|
251
251
|
});
|
|
252
252
|
|
|
253
|
+
it("still validates against pi if provided in answerForms", () => {
|
|
254
|
+
const rubric: Rubric = {
|
|
255
|
+
answers: [
|
|
256
|
+
{
|
|
257
|
+
maxError: null,
|
|
258
|
+
message: "",
|
|
259
|
+
simplify: "required",
|
|
260
|
+
status: "correct",
|
|
261
|
+
strict: false,
|
|
262
|
+
value: 311.01767270538954,
|
|
263
|
+
answerForms: ["pi"],
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
labelText: "",
|
|
267
|
+
size: "normal",
|
|
268
|
+
static: false,
|
|
269
|
+
coefficient: false,
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const userInput = {
|
|
273
|
+
currentValue: "99 pi",
|
|
274
|
+
} as const;
|
|
275
|
+
|
|
276
|
+
const score = NumericInput.validate(userInput, rubric);
|
|
277
|
+
|
|
278
|
+
expect(score).toMatchInlineSnapshot(`
|
|
279
|
+
{
|
|
280
|
+
"earned": 1,
|
|
281
|
+
"message": null,
|
|
282
|
+
"total": 1,
|
|
283
|
+
"type": "points",
|
|
284
|
+
}
|
|
285
|
+
`);
|
|
286
|
+
});
|
|
287
|
+
|
|
253
288
|
it("with a strict answer", () => {
|
|
254
289
|
const rubric: Rubric = {
|
|
255
290
|
answers: [
|