@khanacademy/perseus 1.2.0 → 1.3.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 +11 -0
- package/dist/es/index.css +3 -3
- package/dist/es/index.js +138 -167
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +3 -3
- package/dist/index.js +136 -165
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/styles/widgets/passage.less +7 -3
- package/src/widgets/__stories__/passage-ref.stories.jsx +6 -2
- package/src/widgets/__stories__/passage.stories.jsx +9 -1
- package/src/widgets/__stories__/radio.stories.jsx +11 -0
- package/src/widgets/__testdata__/passage-ref_testdata.js +62 -0
- package/src/widgets/__testdata__/passage_testdata.js +25 -0
- package/src/widgets/__testdata__/radio_testdata.js +52 -0
- package/src/widgets/__tests__/__snapshots__/passage-ref_test.jsx.snap +18 -18
- package/src/widgets/__tests__/__snapshots__/passage_test.jsx.snap +36 -36
- package/src/widgets/__tests__/passage_test.jsx +6 -8
- package/src/widgets/__tests__/radio_test.jsx +3 -1
- package/src/widgets/passage.jsx +12 -41
- package/src/widgets/radio/widget.jsx +128 -129
- package/src/widgets/passage/get-line-height-for-node.js +0 -9
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable react/sort-comp */
|
|
2
1
|
// @flow
|
|
3
2
|
import {linterContextDefault} from "@khanacademy/perseus-linter";
|
|
4
3
|
import * as i18n from "@khanacademy/wonder-blocks-i18n";
|
|
@@ -66,6 +65,134 @@ class Radio extends React.Component<Props> {
|
|
|
66
65
|
linterContext: linterContextDefault,
|
|
67
66
|
};
|
|
68
67
|
|
|
68
|
+
static validate(userInput: UserInput, rubric: Rubric): PerseusScore {
|
|
69
|
+
const numSelected = userInput.choicesSelected.reduce(
|
|
70
|
+
(sum, selected) => {
|
|
71
|
+
return sum + (selected ? 1 : 0);
|
|
72
|
+
},
|
|
73
|
+
0,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
if (numSelected === 0) {
|
|
77
|
+
return {
|
|
78
|
+
type: "invalid",
|
|
79
|
+
message: null,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (
|
|
84
|
+
userInput.numCorrect &&
|
|
85
|
+
userInput.numCorrect > 1 &&
|
|
86
|
+
numSelected !== userInput.numCorrect
|
|
87
|
+
) {
|
|
88
|
+
return {
|
|
89
|
+
type: "invalid",
|
|
90
|
+
message: i18n._("Please choose the correct number of answers."),
|
|
91
|
+
};
|
|
92
|
+
// If NOTA and some other answer are checked, ...
|
|
93
|
+
}
|
|
94
|
+
if (userInput.noneOfTheAboveSelected && numSelected > 1) {
|
|
95
|
+
return {
|
|
96
|
+
type: "invalid",
|
|
97
|
+
message: i18n._(
|
|
98
|
+
"'None of the above' may not be selected " +
|
|
99
|
+
"when other answers are selected.",
|
|
100
|
+
),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const correct = userInput.choicesSelected.every((selected, i) => {
|
|
105
|
+
let isCorrect;
|
|
106
|
+
if (userInput.noneOfTheAboveIndex === i) {
|
|
107
|
+
isCorrect = rubric.choices.every((choice, j) => {
|
|
108
|
+
return i === j || !choice.correct;
|
|
109
|
+
});
|
|
110
|
+
} else {
|
|
111
|
+
isCorrect = !!rubric.choices[i].correct;
|
|
112
|
+
}
|
|
113
|
+
return isCorrect === selected;
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
type: "points",
|
|
118
|
+
earned: correct ? 1 : 0,
|
|
119
|
+
total: 1,
|
|
120
|
+
message: null,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
static getUserInputFromProps(props: Props): UserInput {
|
|
125
|
+
// Return checked inputs in the form {choicesSelected: [bool]}. (Dear
|
|
126
|
+
// future timeline implementers: this used to be {value: i} before
|
|
127
|
+
// multiple select was added)
|
|
128
|
+
if (props.choiceStates) {
|
|
129
|
+
let noneOfTheAboveIndex = null;
|
|
130
|
+
let noneOfTheAboveSelected = false;
|
|
131
|
+
|
|
132
|
+
const choiceStates = props.choiceStates;
|
|
133
|
+
const choicesSelected = choiceStates.map(() => false);
|
|
134
|
+
const countChoices = props.countChoices;
|
|
135
|
+
const numCorrect = props.numCorrect;
|
|
136
|
+
|
|
137
|
+
for (let i = 0; i < choicesSelected.length; i++) {
|
|
138
|
+
const index = props.choices[i].originalIndex;
|
|
139
|
+
|
|
140
|
+
choicesSelected[index] = choiceStates[i].selected;
|
|
141
|
+
|
|
142
|
+
if (props.choices[i].isNoneOfTheAbove) {
|
|
143
|
+
noneOfTheAboveIndex = index;
|
|
144
|
+
|
|
145
|
+
if (choicesSelected[i]) {
|
|
146
|
+
noneOfTheAboveSelected = true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
countChoices,
|
|
153
|
+
choicesSelected,
|
|
154
|
+
numCorrect,
|
|
155
|
+
noneOfTheAboveIndex,
|
|
156
|
+
noneOfTheAboveSelected,
|
|
157
|
+
};
|
|
158
|
+
// Support legacy choiceState implementation
|
|
159
|
+
}
|
|
160
|
+
/* istanbul ignore if - props.values is deprecated */
|
|
161
|
+
const {values} = props;
|
|
162
|
+
if (values) {
|
|
163
|
+
let noneOfTheAboveIndex = null;
|
|
164
|
+
let noneOfTheAboveSelected = false;
|
|
165
|
+
|
|
166
|
+
const choicesSelected = [...values];
|
|
167
|
+
const countChoices = props.countChoices;
|
|
168
|
+
const numCorrect = props.numCorrect;
|
|
169
|
+
const valuesLength = values.length;
|
|
170
|
+
|
|
171
|
+
for (let i = 0; i < valuesLength; i++) {
|
|
172
|
+
const index = props.choices[i].originalIndex;
|
|
173
|
+
choicesSelected[index] = values[i];
|
|
174
|
+
|
|
175
|
+
if (props.choices[i].isNoneOfTheAbove) {
|
|
176
|
+
noneOfTheAboveIndex = index;
|
|
177
|
+
if (choicesSelected[i]) {
|
|
178
|
+
noneOfTheAboveSelected = true;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
choicesSelected,
|
|
184
|
+
noneOfTheAboveIndex,
|
|
185
|
+
noneOfTheAboveSelected,
|
|
186
|
+
countChoices,
|
|
187
|
+
numCorrect,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
// Nothing checked
|
|
191
|
+
return {
|
|
192
|
+
choicesSelected: props.choices.map(() => false),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
69
196
|
_renderRenderer: (content?: string) => React.Node = (
|
|
70
197
|
content?: string = "",
|
|
71
198
|
) => {
|
|
@@ -382,134 +509,6 @@ class Radio extends React.Component<Props> {
|
|
|
382
509
|
/>
|
|
383
510
|
);
|
|
384
511
|
}
|
|
385
|
-
|
|
386
|
-
static validate(userInput: UserInput, rubric: Rubric): PerseusScore {
|
|
387
|
-
const numSelected = userInput.choicesSelected.reduce(
|
|
388
|
-
(sum, selected) => {
|
|
389
|
-
return sum + (selected ? 1 : 0);
|
|
390
|
-
},
|
|
391
|
-
0,
|
|
392
|
-
);
|
|
393
|
-
|
|
394
|
-
if (numSelected === 0) {
|
|
395
|
-
return {
|
|
396
|
-
type: "invalid",
|
|
397
|
-
message: null,
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
if (
|
|
402
|
-
userInput.numCorrect &&
|
|
403
|
-
userInput.numCorrect > 1 &&
|
|
404
|
-
numSelected !== userInput.numCorrect
|
|
405
|
-
) {
|
|
406
|
-
return {
|
|
407
|
-
type: "invalid",
|
|
408
|
-
message: i18n._("Please choose the correct number of answers."),
|
|
409
|
-
};
|
|
410
|
-
// If NOTA and some other answer are checked, ...
|
|
411
|
-
}
|
|
412
|
-
if (userInput.noneOfTheAboveSelected && numSelected > 1) {
|
|
413
|
-
return {
|
|
414
|
-
type: "invalid",
|
|
415
|
-
message: i18n._(
|
|
416
|
-
"'None of the above' may not be selected " +
|
|
417
|
-
"when other answers are selected.",
|
|
418
|
-
),
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
const correct = userInput.choicesSelected.every((selected, i) => {
|
|
423
|
-
let isCorrect;
|
|
424
|
-
if (userInput.noneOfTheAboveIndex === i) {
|
|
425
|
-
isCorrect = rubric.choices.every((choice, j) => {
|
|
426
|
-
return i === j || !choice.correct;
|
|
427
|
-
});
|
|
428
|
-
} else {
|
|
429
|
-
isCorrect = !!rubric.choices[i].correct;
|
|
430
|
-
}
|
|
431
|
-
return isCorrect === selected;
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
return {
|
|
435
|
-
type: "points",
|
|
436
|
-
earned: correct ? 1 : 0,
|
|
437
|
-
total: 1,
|
|
438
|
-
message: null,
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
static getUserInputFromProps(props: Props): UserInput {
|
|
443
|
-
// Return checked inputs in the form {choicesSelected: [bool]}. (Dear
|
|
444
|
-
// future timeline implementers: this used to be {value: i} before
|
|
445
|
-
// multiple select was added)
|
|
446
|
-
if (props.choiceStates) {
|
|
447
|
-
let noneOfTheAboveIndex = null;
|
|
448
|
-
let noneOfTheAboveSelected = false;
|
|
449
|
-
|
|
450
|
-
const choiceStates = props.choiceStates;
|
|
451
|
-
const choicesSelected = choiceStates.map(() => false);
|
|
452
|
-
const countChoices = props.countChoices;
|
|
453
|
-
const numCorrect = props.numCorrect;
|
|
454
|
-
|
|
455
|
-
for (let i = 0; i < choicesSelected.length; i++) {
|
|
456
|
-
const index = props.choices[i].originalIndex;
|
|
457
|
-
|
|
458
|
-
choicesSelected[index] = choiceStates[i].selected;
|
|
459
|
-
|
|
460
|
-
if (props.choices[i].isNoneOfTheAbove) {
|
|
461
|
-
noneOfTheAboveIndex = index;
|
|
462
|
-
|
|
463
|
-
if (choicesSelected[i]) {
|
|
464
|
-
noneOfTheAboveSelected = true;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
return {
|
|
470
|
-
countChoices,
|
|
471
|
-
choicesSelected,
|
|
472
|
-
numCorrect,
|
|
473
|
-
noneOfTheAboveIndex,
|
|
474
|
-
noneOfTheAboveSelected,
|
|
475
|
-
};
|
|
476
|
-
// Support legacy choiceState implementation
|
|
477
|
-
}
|
|
478
|
-
/* istanbul ignore if - props.values is deprecated */
|
|
479
|
-
const {values} = props;
|
|
480
|
-
if (values) {
|
|
481
|
-
let noneOfTheAboveIndex = null;
|
|
482
|
-
let noneOfTheAboveSelected = false;
|
|
483
|
-
|
|
484
|
-
const choicesSelected = [...values];
|
|
485
|
-
const countChoices = props.countChoices;
|
|
486
|
-
const numCorrect = props.numCorrect;
|
|
487
|
-
const valuesLength = values.length;
|
|
488
|
-
|
|
489
|
-
for (let i = 0; i < valuesLength; i++) {
|
|
490
|
-
const index = props.choices[i].originalIndex;
|
|
491
|
-
choicesSelected[index] = values[i];
|
|
492
|
-
|
|
493
|
-
if (props.choices[i].isNoneOfTheAbove) {
|
|
494
|
-
noneOfTheAboveIndex = index;
|
|
495
|
-
if (choicesSelected[i]) {
|
|
496
|
-
noneOfTheAboveSelected = true;
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
return {
|
|
501
|
-
choicesSelected,
|
|
502
|
-
noneOfTheAboveIndex,
|
|
503
|
-
noneOfTheAboveSelected,
|
|
504
|
-
countChoices,
|
|
505
|
-
numCorrect,
|
|
506
|
-
};
|
|
507
|
-
}
|
|
508
|
-
// Nothing checked
|
|
509
|
-
return {
|
|
510
|
-
choicesSelected: props.choices.map(() => false),
|
|
511
|
-
};
|
|
512
|
-
}
|
|
513
512
|
}
|
|
514
513
|
|
|
515
514
|
export default Radio;
|