@khanacademy/perseus 0.3.3 → 0.4.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 +31 -0
- package/dist/es/index.js +8247 -5609
- package/dist/es/index.js.map +1 -1
- package/dist/index.js +17900 -15265
- package/dist/index.js.map +1 -1
- package/dist/strings.js +0 -1
- package/package.json +9 -8
- package/src/__tests__/__snapshots__/item-renderer_test.jsx.snap +2 -3
- package/src/__tests__/__snapshots__/server-item-renderer_test.jsx.snap +2 -3
- package/src/__tests__/renderer-api_test.jsx +3 -27
- package/src/components/__stories__/svg-image.stories.jsx +11 -0
- package/src/components/fixed-to-responsive.jsx +1 -0
- package/src/components/highlighting/ui/util.js +2 -2
- package/src/components/input-with-examples.jsx +3 -1
- package/src/components/svg-image.jsx +10 -7
- package/src/components/text-input.jsx +65 -16
- package/src/definition-context.js +59 -0
- package/src/index.js +5 -1
- package/src/interaction-tracker.js +2 -2
- package/src/item-renderer.jsx +1 -1
- package/src/mixins/provide-keypad.jsx +1 -1
- package/src/multi-items/__tests__/__snapshots__/multi-renderer_test.js.snap +438 -523
- package/src/multi-items/__tests__/multi-renderer_test.js +9 -9
- package/src/renderer.jsx +15 -8
- package/src/server-item-renderer.jsx +1 -1
- package/src/types.js +3 -4
- package/src/util/scroll-utils.js +2 -2
- package/src/widgets/__stories__/definition.stories.jsx +44 -0
- package/src/widgets/__tests__/__snapshots__/categorizer_test.jsx.snap +76 -8
- package/src/widgets/__tests__/__snapshots__/graded-group-set-jipt_test.jsx.snap +6 -9
- package/src/widgets/__tests__/__snapshots__/graded-group-set_test.jsx.snap +2 -3
- package/src/widgets/__tests__/__snapshots__/grapher_test.jsx.snap +19 -2
- package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +338 -390
- package/src/widgets/__tests__/__snapshots__/image_test.jsx.snap +38 -4
- package/src/widgets/__tests__/__snapshots__/interactive-graph_test.jsx.snap +114 -12
- package/src/widgets/__tests__/__snapshots__/matrix_test.jsx.snap +120 -48
- package/src/widgets/__tests__/__snapshots__/numeric-input_test.jsx.snap +8 -12
- package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +3615 -4517
- package/src/widgets/__tests__/graded-group-set-jipt_test.jsx +0 -1
- package/src/widgets/__tests__/grapher.cypress.jsx +236 -176
- package/src/widgets/__tests__/group_test.jsx +4 -4
- package/src/widgets/__tests__/interactive-graph.cypress.jsx +371 -291
- package/src/widgets/__tests__/radio_test.jsx +73 -114
- package/src/widgets/definition.jsx +34 -32
- package/src/widgets/interactive-graph.jsx +4 -4
- package/src/widgets/label-image/answer-choices.jsx +1 -1
- package/src/widgets/label-image.jsx +1 -1
- package/src/widgets/molecule/molecule-drawing.jsx +1 -1
- package/src/widgets/numeric-input.jsx +2 -15
- package/src/widgets/radio/base-radio.jsx +1 -1
- package/src/widgets/radio/choice-icon.jsx +11 -10
- package/src/widgets/radio/choice.jsx +164 -396
- package/src/widgets/radio/cross-out-button.jsx +3 -5
- package/src/widgets/radio/option-status.jsx +4 -5
- package/src/widgets/radio/__stories__/cross-out-menu.stories.jsx +0 -59
- package/src/widgets/radio/__stories__/cross-out-tooltip.stories.jsx +0 -59
- package/src/widgets/radio/__stories__/toggleable-radio-button.stories.jsx +0 -28
- package/src/widgets/radio/cross-out-menu-wrapper.jsx +0 -492
- package/src/widgets/radio/cross-out-tooltip.jsx +0 -129
- package/src/widgets/radio/toggleable-radio-button.jsx +0 -133
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @flow
|
|
2
2
|
|
|
3
|
-
import {screen, fireEvent} from "@testing-library/react";
|
|
3
|
+
import {act, screen, fireEvent} from "@testing-library/react";
|
|
4
4
|
import userEvent from "@testing-library/user-event";
|
|
5
5
|
import _ from "underscore";
|
|
6
6
|
import "@testing-library/jest-dom"; // Imports custom mathers
|
|
@@ -24,23 +24,16 @@ import type {
|
|
|
24
24
|
} from "../../perseus-types.js";
|
|
25
25
|
import type {APIOptions} from "../../types.js";
|
|
26
26
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
// fully rendered.
|
|
30
|
-
jest.runOnlyPendingTimers();
|
|
31
|
-
jest.runOnlyPendingTimers();
|
|
32
|
-
jest.runOnlyPendingTimers();
|
|
33
|
-
};
|
|
27
|
+
const selectOption = (index: number) => {
|
|
28
|
+
const options = screen.getAllByRole("checkbox");
|
|
34
29
|
|
|
35
|
-
const answer = (index: number) => {
|
|
36
|
-
const radios = screen.getAllByRole("radio");
|
|
37
|
-
// userEvent.click() returns a very hard-to-understand error if passed an
|
|
38
30
|
// element that is null/undefined (ie. if the index is invalid) so we
|
|
39
31
|
// manually check and throw here to protect future me, and others :)
|
|
40
|
-
if (index >
|
|
32
|
+
if (index > options.length) {
|
|
41
33
|
throw new Error("Invalid array index for radio");
|
|
42
34
|
}
|
|
43
|
-
|
|
35
|
+
|
|
36
|
+
userEvent.click(options[index]);
|
|
44
37
|
};
|
|
45
38
|
|
|
46
39
|
describe("single-choice question", () => {
|
|
@@ -76,7 +69,7 @@ describe("single-choice question", () => {
|
|
|
76
69
|
const {container} = renderQuestion(question, apiOptions);
|
|
77
70
|
|
|
78
71
|
// Act
|
|
79
|
-
|
|
72
|
+
selectOption(correct);
|
|
80
73
|
|
|
81
74
|
// Assert
|
|
82
75
|
expect(container).toMatchSnapshot("correct answer");
|
|
@@ -87,7 +80,7 @@ describe("single-choice question", () => {
|
|
|
87
80
|
const {container} = renderQuestion(question, apiOptions);
|
|
88
81
|
|
|
89
82
|
// Act
|
|
90
|
-
|
|
83
|
+
selectOption(incorrect[0]);
|
|
91
84
|
|
|
92
85
|
// Assert
|
|
93
86
|
expect(container).toMatchSnapshot("incorrect answer");
|
|
@@ -100,8 +93,7 @@ describe("single-choice question", () => {
|
|
|
100
93
|
});
|
|
101
94
|
|
|
102
95
|
// Act
|
|
103
|
-
|
|
104
|
-
|
|
96
|
+
selectOption(correct);
|
|
105
97
|
// Assert
|
|
106
98
|
expect(renderer).toHaveBeenAnsweredCorrectly();
|
|
107
99
|
});
|
|
@@ -109,7 +101,8 @@ describe("single-choice question", () => {
|
|
|
109
101
|
it("should accept the right answer (touch)", () => {
|
|
110
102
|
// Arrange
|
|
111
103
|
const {renderer} = renderQuestion(question, apiOptions);
|
|
112
|
-
const correctRadio =
|
|
104
|
+
const correctRadio =
|
|
105
|
+
screen.getAllByRole("checkbox")[correct];
|
|
113
106
|
|
|
114
107
|
// Act
|
|
115
108
|
fireEvent.touchStart(correctRadio);
|
|
@@ -131,7 +124,7 @@ describe("single-choice question", () => {
|
|
|
131
124
|
const {renderer} = renderQuestion(question, apiOptions);
|
|
132
125
|
|
|
133
126
|
// Act
|
|
134
|
-
|
|
127
|
+
selectOption(incorrect);
|
|
135
128
|
|
|
136
129
|
// Assert
|
|
137
130
|
expect(renderer).toHaveBeenAnsweredIncorrectly();
|
|
@@ -160,11 +153,11 @@ describe("single-choice question", () => {
|
|
|
160
153
|
// Act
|
|
161
154
|
// Since this is a single-select setup, just select the first
|
|
162
155
|
// incorrect choice.
|
|
163
|
-
|
|
156
|
+
selectOption(incorrect[0]);
|
|
164
157
|
renderer.deselectIncorrectSelectedChoices();
|
|
165
158
|
|
|
166
159
|
// Assert
|
|
167
|
-
screen.getAllByRole("
|
|
160
|
+
screen.getAllByRole("checkbox").forEach((r) => {
|
|
168
161
|
expect(r).not.toBeChecked();
|
|
169
162
|
});
|
|
170
163
|
});
|
|
@@ -184,12 +177,12 @@ describe("single-choice question", () => {
|
|
|
184
177
|
renderQuestion(staticQuestion, apiOptions, {reviewMode});
|
|
185
178
|
|
|
186
179
|
// Act
|
|
187
|
-
|
|
180
|
+
selectOption(correct);
|
|
188
181
|
|
|
189
182
|
// Assert
|
|
190
183
|
// Everything's read-only so no selections made
|
|
191
|
-
screen.getAllByRole("
|
|
192
|
-
expect(r).
|
|
184
|
+
screen.getAllByRole("checkbox").forEach((r) => {
|
|
185
|
+
expect(r).toHaveAttribute("aria-disabled", "true");
|
|
193
186
|
});
|
|
194
187
|
});
|
|
195
188
|
},
|
|
@@ -200,10 +193,12 @@ describe("single-choice question", () => {
|
|
|
200
193
|
renderQuestion(question, apiOptions);
|
|
201
194
|
|
|
202
195
|
// Act
|
|
203
|
-
userEvent.
|
|
196
|
+
userEvent.tab();
|
|
197
|
+
expect(screen.getAllByRole("checkbox")[0]).toHaveFocus();
|
|
198
|
+
userEvent.tab();
|
|
204
199
|
|
|
205
200
|
// Assert
|
|
206
|
-
expect(screen.getAllByRole("
|
|
201
|
+
expect(screen.getAllByRole("checkbox")[1]).toHaveFocus();
|
|
207
202
|
});
|
|
208
203
|
|
|
209
204
|
it("should be able to navigate up by keyboard", () => {
|
|
@@ -211,10 +206,14 @@ describe("single-choice question", () => {
|
|
|
211
206
|
renderQuestion(question, apiOptions);
|
|
212
207
|
|
|
213
208
|
// Act
|
|
214
|
-
userEvent.
|
|
209
|
+
userEvent.tab();
|
|
210
|
+
expect(screen.getAllByRole("checkbox")[0]).toHaveFocus();
|
|
211
|
+
userEvent.tab();
|
|
212
|
+
expect(screen.getAllByRole("checkbox")[1]).toHaveFocus();
|
|
213
|
+
userEvent.tab({shift: true});
|
|
215
214
|
|
|
216
215
|
// Assert
|
|
217
|
-
expect(screen.getAllByRole("
|
|
216
|
+
expect(screen.getAllByRole("checkbox")[0]).toHaveFocus();
|
|
218
217
|
});
|
|
219
218
|
|
|
220
219
|
it("should be able to navigate through 'None of the above' choice by keyboard", () => {
|
|
@@ -225,11 +224,12 @@ describe("single-choice question", () => {
|
|
|
225
224
|
renderQuestion(q, apiOptions);
|
|
226
225
|
|
|
227
226
|
// Act
|
|
228
|
-
userEvent.
|
|
229
|
-
userEvent.
|
|
227
|
+
userEvent.tab();
|
|
228
|
+
userEvent.tab();
|
|
229
|
+
userEvent.tab();
|
|
230
230
|
|
|
231
231
|
// Assert
|
|
232
|
-
expect(screen.getAllByRole("
|
|
232
|
+
expect(screen.getAllByRole("checkbox")[2]).toHaveFocus();
|
|
233
233
|
});
|
|
234
234
|
|
|
235
235
|
it.each([
|
|
@@ -251,7 +251,7 @@ describe("single-choice question", () => {
|
|
|
251
251
|
// We click on the first item, which was the second (index == 1)
|
|
252
252
|
// item in the original choices. But because of enforced ordering,
|
|
253
253
|
// it is now at the top of the list (and thus our correct answer).
|
|
254
|
-
userEvent.click(screen.getAllByRole("
|
|
254
|
+
userEvent.click(screen.getAllByRole("checkbox")[0]);
|
|
255
255
|
|
|
256
256
|
// Assert
|
|
257
257
|
const items = screen.getAllByRole("listitem");
|
|
@@ -316,7 +316,7 @@ describe("single-choice question", () => {
|
|
|
316
316
|
const {renderer} = renderQuestion(question, apiOptions);
|
|
317
317
|
|
|
318
318
|
// Act
|
|
319
|
-
|
|
319
|
+
selectOption(incorrect[0]);
|
|
320
320
|
renderer.showRationalesForCurrentlySelectedChoices();
|
|
321
321
|
|
|
322
322
|
// Assert
|
|
@@ -347,8 +347,9 @@ describe("single-choice question", () => {
|
|
|
347
347
|
).toBeVisible();
|
|
348
348
|
});
|
|
349
349
|
|
|
350
|
-
it("should open the cross-out menu when button clicked", () => {
|
|
350
|
+
it("should open the cross-out menu when button clicked", async () => {
|
|
351
351
|
// Arrange
|
|
352
|
+
|
|
352
353
|
renderQuestion(question, crossOutApiOptions);
|
|
353
354
|
|
|
354
355
|
// Act
|
|
@@ -357,7 +358,9 @@ describe("single-choice question", () => {
|
|
|
357
358
|
name: /Open menu for Choice B/,
|
|
358
359
|
}),
|
|
359
360
|
);
|
|
360
|
-
|
|
361
|
+
await act(async () => {
|
|
362
|
+
await jest.runAllTimers();
|
|
363
|
+
});
|
|
361
364
|
|
|
362
365
|
// Assert
|
|
363
366
|
expect(
|
|
@@ -367,33 +370,39 @@ describe("single-choice question", () => {
|
|
|
367
370
|
).toBeVisible();
|
|
368
371
|
});
|
|
369
372
|
|
|
370
|
-
it("should open the cross-out menu when focused and spacebar pressed", () => {
|
|
373
|
+
it("should open the cross-out menu when focused and spacebar pressed", async () => {
|
|
371
374
|
// Arrange
|
|
375
|
+
|
|
372
376
|
renderQuestion(question, crossOutApiOptions);
|
|
373
377
|
userEvent.tab(); // Choice icon
|
|
374
378
|
userEvent.tab(); // Cross-out menu ellipsis
|
|
375
379
|
|
|
376
380
|
// Act
|
|
377
381
|
userEvent.keyboard("{space}");
|
|
378
|
-
|
|
382
|
+
await act(async () => {
|
|
383
|
+
await jest.runAllTimers();
|
|
384
|
+
});
|
|
379
385
|
|
|
380
386
|
// Assert
|
|
381
387
|
expect(
|
|
382
388
|
screen.getByRole("button", {
|
|
383
389
|
name: /Cross out Choice A/,
|
|
384
390
|
}),
|
|
385
|
-
).
|
|
391
|
+
).toBeVisible();
|
|
386
392
|
});
|
|
387
393
|
|
|
388
|
-
it("should cross-out selection and dismiss button when clicked", () => {
|
|
394
|
+
it("should cross-out selection and dismiss button when clicked", async () => {
|
|
389
395
|
// Arrange
|
|
396
|
+
|
|
390
397
|
renderQuestion(question, crossOutApiOptions);
|
|
391
398
|
userEvent.click(
|
|
392
399
|
screen.getByRole("button", {
|
|
393
400
|
name: /Open menu for Choice B/,
|
|
394
401
|
}),
|
|
395
402
|
);
|
|
396
|
-
|
|
403
|
+
await act(async () => {
|
|
404
|
+
await jest.runAllTimers();
|
|
405
|
+
});
|
|
397
406
|
|
|
398
407
|
// Act
|
|
399
408
|
userEvent.click(
|
|
@@ -401,7 +410,7 @@ describe("single-choice question", () => {
|
|
|
401
410
|
name: /Cross out Choice B/,
|
|
402
411
|
}),
|
|
403
412
|
);
|
|
404
|
-
jest.
|
|
413
|
+
jest.runAllTimers();
|
|
405
414
|
|
|
406
415
|
// Assert
|
|
407
416
|
expect(
|
|
@@ -409,84 +418,34 @@ describe("single-choice question", () => {
|
|
|
409
418
|
name: /Cross out Choice B/,
|
|
410
419
|
}),
|
|
411
420
|
).toHaveLength(0);
|
|
412
|
-
|
|
413
|
-
// The choice widget maintains a "preview" of what the opposite
|
|
414
|
-
// state is for use in the "Cross-out button", but it's lazy in
|
|
415
|
-
// updating the preview (only changing it when the button is
|
|
416
|
-
// revealed). So once we cross-out a choice, we have two elements
|
|
417
|
-
// in the DOM with the `Cross out` text. :shrug:
|
|
418
|
-
expect(
|
|
419
|
-
screen.queryAllByText("(Choice B, Crossed out)"),
|
|
420
|
-
).toHaveLength(2);
|
|
421
421
|
});
|
|
422
422
|
|
|
423
|
-
it("should dismiss cross-out button with {tab} key", () => {
|
|
423
|
+
it("should dismiss cross-out button with {tab} key", async () => {
|
|
424
424
|
// Arrange
|
|
425
|
-
renderQuestion(question, crossOutApiOptions);
|
|
426
|
-
userEvent.tab(); // Choice icon
|
|
427
|
-
userEvent.tab(); // Cross-out menu ellipsis
|
|
428
|
-
|
|
429
|
-
// Open
|
|
430
|
-
userEvent.keyboard("{space}");
|
|
431
|
-
waitForTooltip();
|
|
432
|
-
|
|
433
|
-
expect(
|
|
434
|
-
screen.queryAllByRole("button", {
|
|
435
|
-
name: /Cross out Choice A/,
|
|
436
|
-
}),
|
|
437
|
-
).toHaveLength(1);
|
|
438
|
-
|
|
439
|
-
// Act
|
|
440
|
-
userEvent.tab();
|
|
441
|
-
|
|
442
|
-
// Assert
|
|
443
|
-
expect(
|
|
444
|
-
screen.queryAllByRole("button", {
|
|
445
|
-
name: /Cross out Choice B/,
|
|
446
|
-
}),
|
|
447
|
-
).toHaveLength(0);
|
|
448
|
-
});
|
|
449
425
|
|
|
450
|
-
it("should dismiss cross-out button with {Escape} key", () => {
|
|
451
|
-
// Arrange
|
|
452
426
|
renderQuestion(question, crossOutApiOptions);
|
|
453
|
-
|
|
454
427
|
userEvent.tab(); // Choice icon
|
|
455
428
|
userEvent.tab(); // Cross-out menu ellipsis
|
|
456
|
-
userEvent.keyboard("{space}"); // Open tooltip
|
|
457
|
-
|
|
458
|
-
// Global escape handler only set up after a clock tick
|
|
459
|
-
jest.runOnlyPendingTimers();
|
|
460
429
|
|
|
461
430
|
// Act
|
|
462
|
-
userEvent.keyboard("{
|
|
431
|
+
userEvent.keyboard("{space}");
|
|
432
|
+
await act(async () => {
|
|
433
|
+
await jest.runAllTimers();
|
|
434
|
+
});
|
|
463
435
|
|
|
464
|
-
// Assert
|
|
465
436
|
expect(
|
|
466
|
-
screen.
|
|
467
|
-
name: /Cross out Choice
|
|
437
|
+
screen.getByRole("button", {
|
|
438
|
+
name: /Cross out Choice A/,
|
|
468
439
|
}),
|
|
469
|
-
).
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
it("should dismiss cross-out button with click outside tooltip", () => {
|
|
473
|
-
// Arrange
|
|
474
|
-
renderQuestion(question, crossOutApiOptions);
|
|
475
|
-
|
|
476
|
-
userEvent.tab(); // Choice icon
|
|
477
|
-
userEvent.tab(); // Cross-out menu ellipsis
|
|
478
|
-
userEvent.keyboard("{space}"); // Open tooltip
|
|
479
|
-
|
|
480
|
-
// Global escape handler only set up after a clock tick
|
|
481
|
-
jest.runOnlyPendingTimers();
|
|
440
|
+
).toBeVisible();
|
|
482
441
|
|
|
483
|
-
|
|
484
|
-
|
|
442
|
+
userEvent.keyboard("{space}");
|
|
443
|
+
jest.runAllTimers();
|
|
485
444
|
|
|
486
445
|
// Assert
|
|
487
446
|
expect(
|
|
488
447
|
screen.queryAllByRole("button", {
|
|
489
|
-
name: /Cross out Choice
|
|
448
|
+
name: /Cross out Choice A/,
|
|
490
449
|
}),
|
|
491
450
|
).toHaveLength(0);
|
|
492
451
|
});
|
|
@@ -514,7 +473,7 @@ describe("single-choice question", () => {
|
|
|
514
473
|
renderQuestion(question, apiOptions, {reviewMode: true});
|
|
515
474
|
|
|
516
475
|
// Act
|
|
517
|
-
|
|
476
|
+
selectOption(correct);
|
|
518
477
|
|
|
519
478
|
// Assert
|
|
520
479
|
expect(screen.getAllByText("Correct (selected)")).toHaveLength(1);
|
|
@@ -528,7 +487,7 @@ describe("single-choice question", () => {
|
|
|
528
487
|
renderQuestion(question, apiOptions, {reviewMode: true});
|
|
529
488
|
|
|
530
489
|
// Act
|
|
531
|
-
|
|
490
|
+
selectOption(incorrect[0]);
|
|
532
491
|
|
|
533
492
|
// Assert
|
|
534
493
|
expect(screen.getAllByText("Incorrect (selected)")).toHaveLength(1);
|
|
@@ -554,8 +513,8 @@ describe("multi-choice question", () => {
|
|
|
554
513
|
const {renderer} = renderQuestion(question, apiOptions);
|
|
555
514
|
|
|
556
515
|
// Act
|
|
557
|
-
const
|
|
558
|
-
correct.forEach((i) => userEvent.click(
|
|
516
|
+
const options = screen.getAllByRole("checkbox");
|
|
517
|
+
correct.forEach((i) => userEvent.click(options[i]));
|
|
559
518
|
|
|
560
519
|
// Assert
|
|
561
520
|
expect(renderer).toHaveBeenAnsweredCorrectly();
|
|
@@ -566,8 +525,8 @@ describe("multi-choice question", () => {
|
|
|
566
525
|
const {container} = renderQuestion(question, apiOptions);
|
|
567
526
|
|
|
568
527
|
// Act
|
|
569
|
-
const
|
|
570
|
-
incorrect[0].forEach((i) => userEvent.click(
|
|
528
|
+
const options = screen.getAllByRole("checkbox");
|
|
529
|
+
incorrect[0].forEach((i) => userEvent.click(options[i]));
|
|
571
530
|
|
|
572
531
|
// Assert
|
|
573
532
|
expect(container).toMatchSnapshot("invalid state");
|
|
@@ -610,8 +569,8 @@ describe("multi-choice question", () => {
|
|
|
610
569
|
);
|
|
611
570
|
|
|
612
571
|
// Act
|
|
613
|
-
const
|
|
614
|
-
userEvent.click(
|
|
572
|
+
const option = screen.getAllByRole("checkbox");
|
|
573
|
+
userEvent.click(option[3]); // incorrect
|
|
615
574
|
|
|
616
575
|
// Assert
|
|
617
576
|
expect(renderer).toHaveInvalidInput(
|
|
@@ -626,8 +585,8 @@ describe("multi-choice question", () => {
|
|
|
626
585
|
const {renderer} = renderQuestion(question, apiOptions);
|
|
627
586
|
|
|
628
587
|
// Act
|
|
629
|
-
const
|
|
630
|
-
choices.forEach((i) => userEvent.click(
|
|
588
|
+
const option = screen.getAllByRole("checkbox");
|
|
589
|
+
choices.forEach((i) => userEvent.click(option[i]));
|
|
631
590
|
|
|
632
591
|
// Assert
|
|
633
592
|
expect(renderer).toHaveBeenAnsweredIncorrectly();
|
|
@@ -641,8 +600,8 @@ describe("multi-choice question", () => {
|
|
|
641
600
|
const {renderer} = renderQuestion(question, apiOptions);
|
|
642
601
|
|
|
643
602
|
// Act
|
|
644
|
-
const
|
|
645
|
-
choices.forEach((i) => userEvent.click(
|
|
603
|
+
const option = screen.getAllByRole("checkbox");
|
|
604
|
+
choices.forEach((i) => userEvent.click(option[i]));
|
|
646
605
|
|
|
647
606
|
// Assert
|
|
648
607
|
expect(renderer).toHaveInvalidInput();
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
import Button from "@khanacademy/wonder-blocks-button";
|
|
4
4
|
import Color from "@khanacademy/wonder-blocks-color";
|
|
5
5
|
import {Popover, PopoverContentCore} from "@khanacademy/wonder-blocks-popover";
|
|
6
|
-
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
7
6
|
import * as React from "react";
|
|
8
7
|
|
|
8
|
+
import {DefinitionConsumer} from "../definition-context.js";
|
|
9
9
|
import Renderer from "../renderer.jsx";
|
|
10
10
|
|
|
11
11
|
import type {
|
|
@@ -50,37 +50,41 @@ class Definition extends React.Component<DefinitionProps> {
|
|
|
50
50
|
|
|
51
51
|
render(): React.Node {
|
|
52
52
|
return (
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
<DefinitionConsumer>
|
|
54
|
+
{({activeDefinitionId, setActiveDefinitionId}) => (
|
|
55
|
+
<Popover
|
|
56
|
+
content={
|
|
57
|
+
<PopoverContentCore
|
|
58
|
+
color="white"
|
|
59
|
+
style={styles.tooltipBody}
|
|
60
|
+
closeButtonVisible={true}
|
|
61
|
+
>
|
|
62
|
+
<Renderer
|
|
63
|
+
apiOptions={this.props.apiOptions}
|
|
64
|
+
content={this.props.definition}
|
|
65
|
+
widgets={this.props.widgets}
|
|
66
|
+
/>
|
|
67
|
+
</PopoverContentCore>
|
|
68
|
+
}
|
|
69
|
+
opened={activeDefinitionId === this.props.widgetId}
|
|
70
|
+
onClose={() => setActiveDefinitionId(null)}
|
|
71
|
+
placement="top"
|
|
59
72
|
>
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
kind="tertiary"
|
|
74
|
-
onClick={() => {
|
|
75
|
-
this.props.trackInteraction();
|
|
76
|
-
open();
|
|
77
|
-
}}
|
|
78
|
-
>
|
|
79
|
-
{this.props.togglePrompt}
|
|
80
|
-
</Button>
|
|
81
|
-
</span>
|
|
73
|
+
<span className="perseus-widget-definition">
|
|
74
|
+
<Button
|
|
75
|
+
size="small"
|
|
76
|
+
kind="tertiary"
|
|
77
|
+
onClick={() => {
|
|
78
|
+
this.props.trackInteraction();
|
|
79
|
+
setActiveDefinitionId(this.props.widgetId);
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
{this.props.togglePrompt}
|
|
83
|
+
</Button>
|
|
84
|
+
</span>
|
|
85
|
+
</Popover>
|
|
82
86
|
)}
|
|
83
|
-
</
|
|
87
|
+
</DefinitionConsumer>
|
|
84
88
|
);
|
|
85
89
|
}
|
|
86
90
|
}
|
|
@@ -88,11 +92,9 @@ class Definition extends React.Component<DefinitionProps> {
|
|
|
88
92
|
const styles = {
|
|
89
93
|
tooltipBody: {
|
|
90
94
|
color: Color.offBlack,
|
|
91
|
-
display: "block",
|
|
92
95
|
fontSize: 20,
|
|
93
96
|
fontWeight: 500,
|
|
94
97
|
lineHeight: "30px",
|
|
95
|
-
margin: Spacing.xSmall_8,
|
|
96
98
|
},
|
|
97
99
|
};
|
|
98
100
|
|
|
@@ -392,7 +392,7 @@ class InteractiveGraph extends React.Component<Props, State> {
|
|
|
392
392
|
pointA: ?$FlowFixMe;
|
|
393
393
|
pointB: ?$FlowFixMe;
|
|
394
394
|
pointC: ?$FlowFixMe;
|
|
395
|
-
// eslint-disable-next-line
|
|
395
|
+
// eslint-disable-next-line ft-flow/no-mutable-array
|
|
396
396
|
points: Array<$FlowFixMe>;
|
|
397
397
|
polygon: ?$FlowFixMe;
|
|
398
398
|
shouldResetGraphie: boolean;
|
|
@@ -1356,7 +1356,7 @@ class InteractiveGraph extends React.Component<Props, State> {
|
|
|
1356
1356
|
return true;
|
|
1357
1357
|
}
|
|
1358
1358
|
|
|
1359
|
-
// eslint-disable-next-line
|
|
1359
|
+
// eslint-disable-next-line ft-flow/no-mutable-array
|
|
1360
1360
|
const coords: Array<Coord> = _.invoke(this.points, "coord");
|
|
1361
1361
|
coords[i] = coord;
|
|
1362
1362
|
|
|
@@ -2686,10 +2686,10 @@ class InteractiveGraph extends React.Component<Props, State> {
|
|
|
2686
2686
|
rubric.correct.type === "polygon" &&
|
|
2687
2687
|
state.coords != null
|
|
2688
2688
|
) {
|
|
2689
|
-
const guess: Array<Coord> = state.coords?.slice(); // eslint-disable-line
|
|
2689
|
+
const guess: Array<Coord> = state.coords?.slice(); // eslint-disable-line ft-flow/no-mutable-array
|
|
2690
2690
|
// $FlowFixMe[incompatible-type]
|
|
2691
2691
|
// $FlowFixMe[prop-missing]
|
|
2692
|
-
const correct: Array<Coord> = rubric.correct.coords?.slice(); // eslint-disable-line
|
|
2692
|
+
const correct: Array<Coord> = rubric.correct.coords?.slice(); // eslint-disable-line ft-flow/no-mutable-array
|
|
2693
2693
|
|
|
2694
2694
|
let match;
|
|
2695
2695
|
if (rubric.correct.match === "similar") {
|
|
@@ -185,7 +185,7 @@ export default class AnswerChoices extends React.Component<
|
|
|
185
185
|
AnswerChoicesState,
|
|
186
186
|
> {
|
|
187
187
|
// The rendered answers elements.
|
|
188
|
-
// eslint-disable-next-line
|
|
188
|
+
// eslint-disable-next-line ft-flow/no-mutable-array
|
|
189
189
|
_choices: Array<?AnswerChoice>;
|
|
190
190
|
|
|
191
191
|
constructor(props: AnswerChoicesProps) {
|
|
@@ -85,7 +85,7 @@ type LabelImageState = {|
|
|
|
85
85
|
|
|
86
86
|
class LabelImage extends React.Component<LabelImageProps, LabelImageState> {
|
|
87
87
|
// The rendered markers on the question image for labeling.
|
|
88
|
-
// eslint-disable-next-line
|
|
88
|
+
// eslint-disable-next-line ft-flow/no-mutable-array
|
|
89
89
|
_markers: Array<?Marker>;
|
|
90
90
|
|
|
91
91
|
// The popup component containing the answer choices.
|
|
@@ -159,7 +159,7 @@ function compareElements(item0, item1) {
|
|
|
159
159
|
/**
|
|
160
160
|
* Draw an array of rendering instructions into a 2D canvas drawing context.
|
|
161
161
|
*/
|
|
162
|
-
// eslint-disable-next-line
|
|
162
|
+
// eslint-disable-next-line ft-flow/no-mutable-array
|
|
163
163
|
function draw(ctx: $FlowFixMe, items: Array<$FlowFixMe>) {
|
|
164
164
|
items.sort(compareElements).forEach(drawItem(ctx));
|
|
165
165
|
}
|
|
@@ -10,7 +10,7 @@ import _ from "underscore";
|
|
|
10
10
|
import InputWithExamples from "../components/input-with-examples.jsx";
|
|
11
11
|
import PossibleAnswers from "../components/possible-answers.jsx";
|
|
12
12
|
import SimpleKeypadInput from "../components/simple-keypad-input.jsx";
|
|
13
|
-
import {ApiOptions
|
|
13
|
+
import {ApiOptions} from "../perseus-api.jsx";
|
|
14
14
|
import TexWrangler from "../tex-wrangler.js";
|
|
15
15
|
import KhanAnswerTypes from "../util/answer-types.js";
|
|
16
16
|
import KhanMath from "../util/math.js";
|
|
@@ -292,18 +292,6 @@ export class NumericInput extends React.Component<Props, State> {
|
|
|
292
292
|
return [answerBlurb, !!correct];
|
|
293
293
|
};
|
|
294
294
|
|
|
295
|
-
getClasses: (boolean, $FlowFixMe) => $FlowFixMe = (correct, rubric) => {
|
|
296
|
-
const classes = {};
|
|
297
|
-
classes["perseus-input-size-" + this.props.size] = true;
|
|
298
|
-
classes["perseus-input-right-align"] = this.props.rightAlign;
|
|
299
|
-
classes[ApiClassNames.CORRECT] =
|
|
300
|
-
rubric && correct && this.props.currentValue;
|
|
301
|
-
classes[ApiClassNames.INCORRECT] =
|
|
302
|
-
rubric && !correct && this.props.currentValue;
|
|
303
|
-
classes[ApiClassNames.UNANSWERED] = rubric && !this.props.currentValue;
|
|
304
|
-
return classes;
|
|
305
|
-
};
|
|
306
|
-
|
|
307
295
|
// TODO(Nicole, Jeremy): This is maybe never used and should be removed
|
|
308
296
|
examples: () => $ReadOnlyArray<string> = () => {
|
|
309
297
|
// if the set of specified forms are empty, allow all forms
|
|
@@ -421,8 +409,7 @@ export class NumericInput extends React.Component<Props, State> {
|
|
|
421
409
|
const rubric = this.props.reviewModeRubric;
|
|
422
410
|
const answers = this.getAnswerBlurb(rubric);
|
|
423
411
|
const answerBlurb = answers[0];
|
|
424
|
-
const
|
|
425
|
-
const classes = this.getClasses(correct, rubric);
|
|
412
|
+
const classes = [];
|
|
426
413
|
|
|
427
414
|
let labelText = this.props.labelText;
|
|
428
415
|
if (labelText == null || labelText === "") {
|
|
@@ -464,7 +464,7 @@ class BaseRadio extends React.Component<$FlowFixMe, $FlowFixMe> {
|
|
|
464
464
|
{shouldShowInstructions && (
|
|
465
465
|
<div className={instructionsClassName}>{instructions}</div>
|
|
466
466
|
)}
|
|
467
|
-
<ul className={className}>
|
|
467
|
+
<ul className={className} style={{listStyle: "none"}}>
|
|
468
468
|
{this.props.choices.map(function (choice, i) {
|
|
469
469
|
let Element = Choice;
|
|
470
470
|
const elementProps = {
|