@khanacademy/perseus 0.3.4 → 0.4.1
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 +30 -0
- package/dist/es/index.css +436 -1
- package/dist/es/index.js +8232 -5591
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +436 -1
- package/dist/index.js +17876 -15237
- 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/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/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 +13 -6
- package/src/types.js +3 -4
- 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 -29
- 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();
|
|
@@ -5,6 +5,7 @@ import Color from "@khanacademy/wonder-blocks-color";
|
|
|
5
5
|
import {Popover, PopoverContentCore} from "@khanacademy/wonder-blocks-popover";
|
|
6
6
|
import * as React from "react";
|
|
7
7
|
|
|
8
|
+
import {DefinitionConsumer} from "../definition-context.js";
|
|
8
9
|
import Renderer from "../renderer.jsx";
|
|
9
10
|
|
|
10
11
|
import type {
|
|
@@ -49,37 +50,41 @@ class Definition extends React.Component<DefinitionProps> {
|
|
|
49
50
|
|
|
50
51
|
render(): React.Node {
|
|
51
52
|
return (
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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"
|
|
58
72
|
>
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
kind="tertiary"
|
|
73
|
-
onClick={() => {
|
|
74
|
-
this.props.trackInteraction();
|
|
75
|
-
open();
|
|
76
|
-
}}
|
|
77
|
-
>
|
|
78
|
-
{this.props.togglePrompt}
|
|
79
|
-
</Button>
|
|
80
|
-
</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>
|
|
81
86
|
)}
|
|
82
|
-
</
|
|
87
|
+
</DefinitionConsumer>
|
|
83
88
|
);
|
|
84
89
|
}
|
|
85
90
|
}
|
|
@@ -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 = {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @flow
|
|
2
2
|
/* Component for rendering a letter icon in radio choice */
|
|
3
3
|
|
|
4
|
+
import Color from "@khanacademy/wonder-blocks-color";
|
|
4
5
|
import * as i18n from "@khanacademy/wonder-blocks-i18n";
|
|
5
6
|
import {StyleSheet, css} from "aphrodite";
|
|
6
7
|
import * as React from "react";
|
|
@@ -147,9 +148,7 @@ class LibraryChoiceIcon extends React.Component<{
|
|
|
147
148
|
// Note: kaGreen is not only the default product color,
|
|
148
149
|
// but also the "correctness" color
|
|
149
150
|
const bg =
|
|
150
|
-
showCorrectness && correct
|
|
151
|
-
? styleConstants.kaGreen
|
|
152
|
-
: primaryProductColor;
|
|
151
|
+
showCorrectness && correct ? Color.green : primaryProductColor;
|
|
153
152
|
return {
|
|
154
153
|
color: styleConstants.white,
|
|
155
154
|
backgroundColor: bg,
|
|
@@ -157,8 +156,8 @@ class LibraryChoiceIcon extends React.Component<{
|
|
|
157
156
|
};
|
|
158
157
|
}
|
|
159
158
|
return {
|
|
160
|
-
borderColor:
|
|
161
|
-
color:
|
|
159
|
+
borderColor: Color.offBlack64,
|
|
160
|
+
color: Color.offBlack64,
|
|
162
161
|
};
|
|
163
162
|
}
|
|
164
163
|
|
|
@@ -258,7 +257,6 @@ type ChoiceIconProps = {|
|
|
|
258
257
|
// TODO(amy): if we go this "product" flag route, define this type
|
|
259
258
|
// somewhere shared
|
|
260
259
|
product: "sat" | "library",
|
|
261
|
-
primaryProductColor: string,
|
|
262
260
|
reviewMode: boolean,
|
|
263
261
|
previouslyAnswered: boolean,
|
|
264
262
|
// TODO(mdr): The CrossOutButton needs a transparent-background ChoiceIcon,
|
|
@@ -266,10 +264,13 @@ type ChoiceIconProps = {|
|
|
|
266
264
|
// general case, though? When does the choice container have a
|
|
267
265
|
// non-white background, aside from SAT, which uses a different icon?
|
|
268
266
|
transparentBackground?: boolean,
|
|
267
|
+
|
|
268
|
+
primaryProductColor: string,
|
|
269
269
|
|};
|
|
270
|
+
|
|
270
271
|
class ChoiceIcon extends React.Component<ChoiceIconProps> {
|
|
271
272
|
static defaultProps: {|primaryProductColor: string|} = {
|
|
272
|
-
primaryProductColor:
|
|
273
|
+
primaryProductColor: Color.blue,
|
|
273
274
|
};
|
|
274
275
|
|
|
275
276
|
a11yText(letter: string): string {
|
|
@@ -440,9 +441,9 @@ const styles = StyleSheet.create({
|
|
|
440
441
|
},
|
|
441
442
|
|
|
442
443
|
libraryCircleIncorrectAnswered: {
|
|
443
|
-
backgroundColor:
|
|
444
|
-
borderColor:
|
|
445
|
-
color:
|
|
444
|
+
backgroundColor: Color.red,
|
|
445
|
+
borderColor: Color.red,
|
|
446
|
+
color: Color.white,
|
|
446
447
|
},
|
|
447
448
|
|
|
448
449
|
letter: {
|