@khanacademy/perseus 13.2.0 → 15.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 +47 -0
- package/dist/article-renderer.d.ts +1 -2
- package/dist/components/svg-image.d.ts +10 -9
- package/dist/es/index.js +51 -498
- package/dist/es/index.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +72 -511
- package/dist/index.js.map +1 -1
- package/dist/multi-items/item-types.d.ts +1 -1
- package/dist/multi-items/multi-renderer.d.ts +2 -2
- package/dist/perseus-api.d.ts +0 -3
- package/dist/server-item-renderer.d.ts +2 -15
- package/dist/strings.js +1 -0
- package/dist/styles/shared.d.ts +1 -0
- package/dist/types.d.ts +0 -2
- package/dist/util/a11y.d.ts +1 -0
- package/dist/widgets/expression.d.ts +0 -12
- package/dist/widgets/graded-group.d.ts +1 -1
- package/dist/widgets/label-image.d.ts +1 -1
- package/dist/widgets/matcher.d.ts +0 -1
- package/dist/widgets/passage.d.ts +1 -1
- package/dist/widgets/radio/widget.d.ts +1 -1
- package/dist/widgets/simulator.d.ts +0 -1
- package/dist/widgets/sorter.d.ts +0 -1
- package/package.json +8 -8
- package/src/__stories__/server-item-renderer.stories.tsx +1 -1
- package/src/__testdata__/server-item-renderer.testdata.ts +160 -0
- package/src/__tests__/article-renderer.test.tsx +2 -3
- package/src/__tests__/server-item-renderer.test.tsx +2 -2
- package/src/article-renderer.tsx +13 -15
- package/src/components/graph.tsx +14 -8
- package/src/components/svg-image.tsx +14 -12
- package/src/hints-renderer.tsx +1 -0
- package/src/index.ts +0 -1
- package/src/multi-items/item-types.ts +1 -1
- package/src/multi-items/multi-renderer.tsx +4 -4
- package/src/perseus-api.tsx +0 -6
- package/src/perseus-types.ts +8 -3
- package/src/server-item-renderer.tsx +12 -13
- package/src/types.ts +0 -5
- package/src/util.ts +1 -1
- package/src/widgets/__stories__/expression.stories.tsx +0 -1
- 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__/expression-mobile.test.tsx +0 -2
- package/src/widgets/__tests__/expression.test.tsx +4 -15
- package/src/widgets/__tests__/randomizers.test.ts +53 -0
- package/src/widgets/expression.tsx +1 -5
- package/src/widgets/graded-group.tsx +1 -1
- package/src/widgets/image.tsx +27 -20
- package/src/widgets/label-image/strings.ts +3 -1
- package/src/widgets/label-image.tsx +2 -2
- package/src/widgets/passage-ref.tsx +1 -1
- package/src/widgets/passage.tsx +1 -1
- package/src/widgets/radio/widget.tsx +1 -1
- package/src/widgets/radio.ts +1 -1
- package/tsconfig-build.tsbuildinfo +1 -1
- package/dist/item-renderer.d.ts +0 -125
- package/dist/mixins/provide-keypad.d.ts +0 -39
- package/src/__stories__/item-renderer.stories.tsx +0 -30
- package/src/__testdata__/item-renderer.testdata.ts +0 -299
- package/src/__tests__/__snapshots__/item-renderer.test.tsx.snap +0 -224
- package/src/__tests__/item-renderer.test.tsx +0 -930
- package/src/item-renderer.tsx +0 -529
- package/src/mixins/provide-keypad.tsx +0 -138
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import {
|
|
2
|
+
arrayOfLength,
|
|
3
|
+
randomBoolean,
|
|
4
|
+
randomElement,
|
|
5
|
+
randomInteger,
|
|
6
|
+
randomLetter,
|
|
7
|
+
randomWord,
|
|
8
|
+
} from "./randomizers";
|
|
9
|
+
|
|
10
|
+
import type {Coord} from "../../interactive2/types";
|
|
11
|
+
import type {
|
|
12
|
+
PerseusGraphType,
|
|
13
|
+
PerseusGraphTypeAngle,
|
|
14
|
+
PerseusGraphTypeCircle,
|
|
15
|
+
PerseusGraphTypeLinear,
|
|
16
|
+
PerseusGraphTypeLinearSystem,
|
|
17
|
+
PerseusGraphTypePoint,
|
|
18
|
+
PerseusGraphTypePolygon,
|
|
19
|
+
PerseusGraphTypeQuadratic,
|
|
20
|
+
PerseusGraphTypeRay,
|
|
21
|
+
PerseusGraphTypeSegment,
|
|
22
|
+
PerseusGraphTypeSinusoid,
|
|
23
|
+
PerseusImageBackground,
|
|
24
|
+
PerseusRenderer,
|
|
25
|
+
} from "../../perseus-types";
|
|
26
|
+
|
|
27
|
+
const randomCoord = (): Coord => {
|
|
28
|
+
return [randomInteger(1, 100), randomInteger(1, 100)];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const randomGraphTypeAngle = (): PerseusGraphTypeAngle => {
|
|
32
|
+
return {
|
|
33
|
+
type: "angle",
|
|
34
|
+
showAngles: randomBoolean(),
|
|
35
|
+
allowReflexAngles: randomBoolean(),
|
|
36
|
+
angleOffsetDeg: randomInteger(1, 90),
|
|
37
|
+
snapDegrees: randomInteger(1, 90),
|
|
38
|
+
match: "congruent",
|
|
39
|
+
// must be 3 coords for an angle graph
|
|
40
|
+
coords: arrayOfLength(3).map(randomCoord),
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const randomGraphTypeCircle = (): PerseusGraphTypeCircle => {
|
|
45
|
+
return {
|
|
46
|
+
type: "circle",
|
|
47
|
+
center: randomCoord(),
|
|
48
|
+
radius: randomInteger(1, 20),
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const randomGraphTypeLinear = (): PerseusGraphTypeLinear => {
|
|
53
|
+
return {
|
|
54
|
+
type: "linear",
|
|
55
|
+
coords: arrayOfLength(2).map(randomCoord),
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const randomGraphTypeLinearSystem = (): PerseusGraphTypeLinearSystem => {
|
|
60
|
+
return {
|
|
61
|
+
type: "linear-system",
|
|
62
|
+
coords: arrayOfLength(2).map(() => arrayOfLength(2).map(randomCoord)),
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const randomGraphTypePoint = (): PerseusGraphTypePoint => {
|
|
67
|
+
let minCoords = 1;
|
|
68
|
+
const numPoints = randomElement(["unlimited", randomInteger(1, 10)]);
|
|
69
|
+
if (typeof numPoints === "number") {
|
|
70
|
+
minCoords = numPoints;
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
type: "point",
|
|
74
|
+
numPoints: typeof numPoints === "number" ? numPoints : "unlimited",
|
|
75
|
+
coords: arrayOfLength(randomInteger(minCoords, 10)).map(randomCoord),
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const randomGraphTypePolygon = (): PerseusGraphTypePolygon => {
|
|
80
|
+
// Note Jhead - I saw this create at least one broken graph but didn't figure out why
|
|
81
|
+
return {
|
|
82
|
+
type: "polygon",
|
|
83
|
+
numSides: randomElement([
|
|
84
|
+
"unlimited",
|
|
85
|
+
undefined,
|
|
86
|
+
randomInteger(3, 30),
|
|
87
|
+
randomInteger(3, 30),
|
|
88
|
+
]),
|
|
89
|
+
showAngles: randomBoolean(),
|
|
90
|
+
showSides: randomBoolean(),
|
|
91
|
+
snapTo: randomElement(["grid", "angles", "sides"]),
|
|
92
|
+
match: randomElement(["similar", "congruent", "approx", undefined]),
|
|
93
|
+
coords: arrayOfLength(randomInteger(3, 20)).map(randomCoord),
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const randomGraphTypeQuadratic = (): PerseusGraphTypeQuadratic => {
|
|
98
|
+
return {
|
|
99
|
+
type: "quadratic",
|
|
100
|
+
coords: arrayOfLength(3).map(randomCoord),
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const randomGraphTypeRay = (): PerseusGraphTypeRay => {
|
|
105
|
+
return {
|
|
106
|
+
type: "ray",
|
|
107
|
+
coords: arrayOfLength(2).map(randomCoord),
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const randomGraphTypeSegment = (): PerseusGraphTypeSegment => {
|
|
112
|
+
const numSegments = randomInteger(1, 6);
|
|
113
|
+
return {
|
|
114
|
+
type: "segment",
|
|
115
|
+
numSegments,
|
|
116
|
+
coords: arrayOfLength(numSegments).map(() => [
|
|
117
|
+
randomCoord(),
|
|
118
|
+
randomCoord(),
|
|
119
|
+
]),
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const randomGraphTypeSinusoid = (): PerseusGraphTypeSinusoid => {
|
|
124
|
+
return {
|
|
125
|
+
type: "sinusoid",
|
|
126
|
+
coords: arrayOfLength(2).map(randomCoord),
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export const randomBackgroundImage = (): PerseusImageBackground => {
|
|
131
|
+
const height = randomInteger(200, 500);
|
|
132
|
+
const width = randomInteger(200, 500);
|
|
133
|
+
const graphieUrl =
|
|
134
|
+
"web+graphie://ka-perseus-graphie.s3.amazonaws.com/a1b7a05c177742523250b64a3995c9b37aac3399";
|
|
135
|
+
const placeKittenUrl = `https://placekitten.com/g/${width}/${height}`;
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
scale: randomInteger(8, 12) / 10,
|
|
139
|
+
bottom: randomInteger(0, 10) - 5,
|
|
140
|
+
url: randomElement([graphieUrl, placeKittenUrl, null]),
|
|
141
|
+
height,
|
|
142
|
+
width,
|
|
143
|
+
left: randomInteger(0, 10) - 5,
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export const randomInteractiveGraphGenerator = (
|
|
148
|
+
type?: string,
|
|
149
|
+
): PerseusRenderer => {
|
|
150
|
+
const randomRange = (): [number, number] => {
|
|
151
|
+
const val1 = randomInteger(0, 400) - 200;
|
|
152
|
+
const val2 = randomInteger(0, 400) - 200;
|
|
153
|
+
if (val1 < val2) {
|
|
154
|
+
return [val1, val2];
|
|
155
|
+
}
|
|
156
|
+
return [val2, val1];
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const graphType =
|
|
160
|
+
type ||
|
|
161
|
+
randomElement([
|
|
162
|
+
"angle",
|
|
163
|
+
"circle",
|
|
164
|
+
"linear",
|
|
165
|
+
"linear-system",
|
|
166
|
+
"point",
|
|
167
|
+
"polygon",
|
|
168
|
+
"quadratic",
|
|
169
|
+
"ray",
|
|
170
|
+
"segment",
|
|
171
|
+
"sinusoid",
|
|
172
|
+
]);
|
|
173
|
+
|
|
174
|
+
let graph: PerseusGraphType;
|
|
175
|
+
let correct: PerseusGraphType;
|
|
176
|
+
|
|
177
|
+
switch (graphType) {
|
|
178
|
+
case "angle": {
|
|
179
|
+
graph = randomGraphTypeAngle();
|
|
180
|
+
correct = randomGraphTypeAngle();
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
case "circle": {
|
|
184
|
+
graph = randomGraphTypeCircle();
|
|
185
|
+
correct = randomGraphTypeCircle();
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
case "linear": {
|
|
189
|
+
graph = randomGraphTypeLinear();
|
|
190
|
+
correct = randomGraphTypeLinear();
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
case "linear-system": {
|
|
194
|
+
graph = randomGraphTypeLinearSystem();
|
|
195
|
+
correct = randomGraphTypeLinearSystem();
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
case "point": {
|
|
199
|
+
graph = randomGraphTypePoint();
|
|
200
|
+
correct = randomGraphTypePoint();
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
case "polygon": {
|
|
204
|
+
graph = randomGraphTypePolygon();
|
|
205
|
+
correct = randomGraphTypePolygon();
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
case "quadratic": {
|
|
209
|
+
graph = randomGraphTypeQuadratic();
|
|
210
|
+
correct = randomGraphTypeQuadratic();
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
case "ray": {
|
|
214
|
+
graph = randomGraphTypeRay();
|
|
215
|
+
correct = randomGraphTypeRay();
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
case "segment": {
|
|
219
|
+
graph = randomGraphTypeSegment();
|
|
220
|
+
correct = randomGraphTypeSegment();
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
case "sinusoid": {
|
|
224
|
+
graph = randomGraphTypeSinusoid();
|
|
225
|
+
correct = randomGraphTypeSinusoid();
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
default: {
|
|
229
|
+
graph = randomGraphTypeAngle();
|
|
230
|
+
correct = randomGraphTypeAngle();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
content: `[[\u2603 interactive-graph 1]]`,
|
|
236
|
+
images: {},
|
|
237
|
+
widgets: {
|
|
238
|
+
"interactive-graph 1": {
|
|
239
|
+
graded: randomBoolean(),
|
|
240
|
+
version: {
|
|
241
|
+
major: 0,
|
|
242
|
+
minor: 0,
|
|
243
|
+
},
|
|
244
|
+
static: false, //always false for this widget
|
|
245
|
+
type: "interactive-graph",
|
|
246
|
+
options: {
|
|
247
|
+
rulerTicks: randomInteger(1, 100),
|
|
248
|
+
showProtractor: randomBoolean(0.2),
|
|
249
|
+
graph,
|
|
250
|
+
snapStep: [
|
|
251
|
+
randomInteger(1, 10) / 10,
|
|
252
|
+
randomInteger(1, 10) / 10,
|
|
253
|
+
],
|
|
254
|
+
labels: [randomLetter() || "x", randomLetter() || "y"],
|
|
255
|
+
step: [randomInteger(1, 10), randomInteger(1, 10)],
|
|
256
|
+
gridStep: [randomInteger(1, 10), randomInteger(1, 10)],
|
|
257
|
+
backgroundImage: randomBackgroundImage(),
|
|
258
|
+
range: [randomRange(), randomRange()],
|
|
259
|
+
showRuler: randomBoolean(0.2),
|
|
260
|
+
markings:
|
|
261
|
+
randomElement(["none", "graph", "grid"]) || "none",
|
|
262
|
+
rulerLabel: randomElement(["", randomWord()]) || "",
|
|
263
|
+
correct,
|
|
264
|
+
},
|
|
265
|
+
alignment: "default",
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
};
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
arrayOfLength,
|
|
3
|
+
randomBoolean,
|
|
4
|
+
randomInteger,
|
|
5
|
+
randomSentence,
|
|
6
|
+
} from "./randomizers";
|
|
7
|
+
|
|
1
8
|
import type {
|
|
2
9
|
PerseusRenderer,
|
|
3
10
|
RadioWidget,
|
|
4
11
|
PassageWidget,
|
|
12
|
+
PerseusRadioChoice,
|
|
5
13
|
} from "../../perseus-types";
|
|
6
14
|
|
|
7
15
|
export const question: PerseusRenderer = {
|
|
@@ -259,3 +267,58 @@ export const multiChoiceQuestionAndAnswer: [
|
|
|
259
267
|
[0, 1, 2, 3],
|
|
260
268
|
],
|
|
261
269
|
];
|
|
270
|
+
|
|
271
|
+
export const randomRadioGenerator = (): PerseusRenderer => {
|
|
272
|
+
const randomChoice = (
|
|
273
|
+
isMultiSelect: boolean,
|
|
274
|
+
isCorrect: boolean,
|
|
275
|
+
isNoneOfTheAbove: boolean,
|
|
276
|
+
): PerseusRadioChoice => {
|
|
277
|
+
return {
|
|
278
|
+
content: randomSentence(12),
|
|
279
|
+
isNoneOfTheAbove: isNoneOfTheAbove,
|
|
280
|
+
correct: isMultiSelect ? randomBoolean() : isCorrect,
|
|
281
|
+
};
|
|
282
|
+
};
|
|
283
|
+
const numberOfChoices = randomInteger(2, 6);
|
|
284
|
+
|
|
285
|
+
const isMultiSelect = randomBoolean();
|
|
286
|
+
// only used if not multi select
|
|
287
|
+
const correctIndex = randomInteger(0, numberOfChoices);
|
|
288
|
+
|
|
289
|
+
const containsNoneOfTheAbove = randomBoolean();
|
|
290
|
+
// only used if containsNoneOfTheAbove
|
|
291
|
+
const noneOfTheAboveIndex = randomInteger(0, numberOfChoices);
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
content: `${randomSentence(30)}\n\n[[\u2603 radio 1]]`,
|
|
295
|
+
images: {},
|
|
296
|
+
widgets: {
|
|
297
|
+
"radio 1": {
|
|
298
|
+
graded: randomBoolean(),
|
|
299
|
+
version: {
|
|
300
|
+
major: 1,
|
|
301
|
+
minor: 0,
|
|
302
|
+
},
|
|
303
|
+
static: randomBoolean(0.05),
|
|
304
|
+
type: "radio",
|
|
305
|
+
options: {
|
|
306
|
+
onePerLine: randomBoolean(),
|
|
307
|
+
displayCount: null,
|
|
308
|
+
choices: arrayOfLength(numberOfChoices).map((_, i) =>
|
|
309
|
+
randomChoice(
|
|
310
|
+
isMultiSelect,
|
|
311
|
+
correctIndex === i,
|
|
312
|
+
containsNoneOfTheAbove && noneOfTheAboveIndex === i,
|
|
313
|
+
),
|
|
314
|
+
),
|
|
315
|
+
hasNoneOfTheAbove: randomBoolean(),
|
|
316
|
+
multipleSelect: isMultiSelect,
|
|
317
|
+
randomize: randomBoolean(),
|
|
318
|
+
deselectEnabled: randomBoolean(),
|
|
319
|
+
},
|
|
320
|
+
alignment: "default",
|
|
321
|
+
} as RadioWidget,
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Helpers to generate random test data for widgets
|
|
2
|
+
|
|
3
|
+
// Returns a random letter inclusive of emoji and japanese characters
|
|
4
|
+
export const randomLetter = (): string => {
|
|
5
|
+
const alphabet = "abcdefghijklmnopqrstuvwxyz".split("");
|
|
6
|
+
const emojis = ["🍇", "🍈", "🍌"];
|
|
7
|
+
const japaneseCharacters = ["ツ", "デ", "ト", "ド", "ナ", "ぁ", "あ"];
|
|
8
|
+
const characters = alphabet.concat(emojis).concat(japaneseCharacters);
|
|
9
|
+
const letter = randomInteger(0, characters.length - 1);
|
|
10
|
+
return characters[letter];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Returns a random word 3-20 characters long composed of the random letter function
|
|
14
|
+
export const randomWord = (): string => {
|
|
15
|
+
// words are 3 - 20 characters
|
|
16
|
+
const numLetters = randomInteger(3, 11);
|
|
17
|
+
return arrayOfLength(numLetters).map(randomLetter).join("");
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Returns a random sentence of a specified number of words, using the random word function
|
|
21
|
+
export const randomSentence = (maxWords: number): string => {
|
|
22
|
+
const numWords = randomInteger(5, Math.max(maxWords, 5));
|
|
23
|
+
return arrayOfLength(numWords).map(randomWord).join(" ");
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// generates a boolean. 50/50 unless rateTrue is specified
|
|
27
|
+
export const randomBoolean = (rateTrue?: number): boolean => {
|
|
28
|
+
let threshold = rateTrue;
|
|
29
|
+
if (threshold == null) {
|
|
30
|
+
threshold = 0.5;
|
|
31
|
+
}
|
|
32
|
+
return Math.random() < threshold;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// generates an integer inclusive of the specified min and max
|
|
36
|
+
export const randomInteger = (min: number, max: number): number => {
|
|
37
|
+
return Math.floor(Math.random() * (max + 1 - min)) + min;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// A helper to create an array of a certain length.
|
|
41
|
+
export const arrayOfLength = (length: number): number[] => {
|
|
42
|
+
return Array.from(Array(length).keys());
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Returns a random element from a provided list
|
|
46
|
+
export function randomElement<T>(items: Array<T>): T | undefined {
|
|
47
|
+
if (items.length > 0) {
|
|
48
|
+
return items[randomInteger(0, items.length - 1)];
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
@@ -37,7 +37,6 @@ function RendererWithContext({item}) {
|
|
|
37
37
|
apiOptions={{
|
|
38
38
|
isMobile: true,
|
|
39
39
|
customKeypad: true,
|
|
40
|
-
useV2Keypad: true,
|
|
41
40
|
}}
|
|
42
41
|
item={item}
|
|
43
42
|
problemNum={0}
|
|
@@ -60,7 +59,6 @@ function KeypadWithContext() {
|
|
|
60
59
|
onElementMounted={setKeypadElement}
|
|
61
60
|
onDismiss={() => {}}
|
|
62
61
|
onAnalyticsEvent={async () => {}}
|
|
63
|
-
useV2Keypad
|
|
64
62
|
/>
|
|
65
63
|
);
|
|
66
64
|
}}
|
|
@@ -42,7 +42,7 @@ const assertCorrect = (itemData: PerseusItem, input: string) => {
|
|
|
42
42
|
type: "perseus:expression-evaluated",
|
|
43
43
|
payload: {
|
|
44
44
|
result: "correct",
|
|
45
|
-
virtualKeypadVersion: "
|
|
45
|
+
virtualKeypadVersion: "MATH_INPUT_KEYPAD_V2",
|
|
46
46
|
},
|
|
47
47
|
});
|
|
48
48
|
};
|
|
@@ -54,7 +54,7 @@ const assertIncorrect = (itemData: PerseusItem, input: string) => {
|
|
|
54
54
|
type: "perseus:expression-evaluated",
|
|
55
55
|
payload: {
|
|
56
56
|
result: "incorrect",
|
|
57
|
-
virtualKeypadVersion: "
|
|
57
|
+
virtualKeypadVersion: "MATH_INPUT_KEYPAD_V2",
|
|
58
58
|
},
|
|
59
59
|
});
|
|
60
60
|
};
|
|
@@ -224,19 +224,8 @@ describe("Expression Widget", function () {
|
|
|
224
224
|
);
|
|
225
225
|
});
|
|
226
226
|
|
|
227
|
-
it("should
|
|
228
|
-
assertKeypadVersion({
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it("should set the virtual keypad version to MATH_INPUT_KEYPAD_V2 when customKeypad is set and useV2Keypad is true", () => {
|
|
232
|
-
assertKeypadVersion(
|
|
233
|
-
{customKeypad: true, useV2Keypad: true},
|
|
234
|
-
"MATH_INPUT_KEYPAD_V2",
|
|
235
|
-
);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it("should default the virtual keypad version to PERSEUS_MATH_INPUT", () => {
|
|
239
|
-
assertKeypadVersion(Object.freeze({}), "PERSEUS_MATH_INPUT");
|
|
227
|
+
it("should default the virtual keypad version to MATH_INPUT_KEYPAD_V2", () => {
|
|
228
|
+
assertKeypadVersion(Object.freeze({}), "MATH_INPUT_KEYPAD_V2");
|
|
240
229
|
});
|
|
241
230
|
});
|
|
242
231
|
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {
|
|
2
|
+
arrayOfLength,
|
|
3
|
+
randomBoolean,
|
|
4
|
+
randomElement,
|
|
5
|
+
randomLetter,
|
|
6
|
+
randomSentence,
|
|
7
|
+
randomWord,
|
|
8
|
+
} from "../__testdata__/randomizers";
|
|
9
|
+
|
|
10
|
+
describe("randomizers", () => {
|
|
11
|
+
describe("randomLetter", () => {
|
|
12
|
+
it("returns one letter", () => {
|
|
13
|
+
// can't check for just one character because some non english chars are longer than 1 char
|
|
14
|
+
expect(typeof randomLetter()).toBe("string");
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe("randomWord", () => {
|
|
19
|
+
it("returns a word of at least 3 letters", () => {
|
|
20
|
+
expect(randomWord().length).toBeGreaterThanOrEqual(3);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("randomSentence", () => {
|
|
25
|
+
it("returns a sentence of at least 5 words", () => {
|
|
26
|
+
expect(randomSentence(10).split(" ").length).toBeGreaterThanOrEqual(
|
|
27
|
+
5,
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("randomBoolean", () => {
|
|
33
|
+
it("is true when rateTrue is 1", () => {
|
|
34
|
+
expect(randomBoolean(1)).toEqual(true);
|
|
35
|
+
});
|
|
36
|
+
it("is false when rateTrue is 0", () => {
|
|
37
|
+
expect(randomBoolean(0)).toEqual(false);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("arrayOfLength", () => {
|
|
42
|
+
it("returns an array of the specified length", () => {
|
|
43
|
+
expect(arrayOfLength(3).length).toEqual(3);
|
|
44
|
+
expect(arrayOfLength(12).length).toEqual(12);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("randomElement", () => {
|
|
49
|
+
it("returns one of the input array elements", () => {
|
|
50
|
+
expect([1, 2, 3]).toContain(randomElement([1, 2, 3]));
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -64,11 +64,7 @@ const deriveKeypadVersion = (apiOptions: APIOptions) => {
|
|
|
64
64
|
// can remove this `virtualKeypadVersion` field entirely.
|
|
65
65
|
return apiOptions.nativeKeypadProxy != null
|
|
66
66
|
? "REACT_NATIVE_KEYPAD"
|
|
67
|
-
:
|
|
68
|
-
? apiOptions.useV2Keypad === true
|
|
69
|
-
? "MATH_INPUT_KEYPAD_V2"
|
|
70
|
-
: "MATH_INPUT_KEYPAD_V1"
|
|
71
|
-
: "PERSEUS_MATH_INPUT";
|
|
67
|
+
: "MATH_INPUT_KEYPAD_V2";
|
|
72
68
|
};
|
|
73
69
|
|
|
74
70
|
type Rubric = PerseusExpressionWidgetOptions;
|
|
@@ -24,6 +24,7 @@ import a11y from "../util/a11y";
|
|
|
24
24
|
|
|
25
25
|
import GradedGroupAnswerBar from "./graded-group-answer-bar";
|
|
26
26
|
|
|
27
|
+
import type {ANSWER_BAR_STATES} from "./graded-group-answer-bar";
|
|
27
28
|
import type {PerseusGradedGroupWidgetOptions} from "../perseus-types";
|
|
28
29
|
import type {
|
|
29
30
|
PerseusScore,
|
|
@@ -31,7 +32,6 @@ import type {
|
|
|
31
32
|
WidgetExports,
|
|
32
33
|
WidgetProps,
|
|
33
34
|
} from "../types";
|
|
34
|
-
import type {ANSWER_BAR_STATES} from "./graded-group-answer-bar";
|
|
35
35
|
|
|
36
36
|
const GRADING_STATUSES = {
|
|
37
37
|
ungraded: "ungraded" as const,
|
package/src/widgets/image.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import classNames from "classnames";
|
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
import _ from "underscore";
|
|
6
6
|
|
|
7
|
+
import AssetContext from "../asset-context";
|
|
7
8
|
import SvgImage from "../components/svg-image";
|
|
8
9
|
import * as Changeable from "../mixins/changeable";
|
|
9
10
|
import Renderer from "../renderer";
|
|
@@ -93,11 +94,14 @@ class ImageWidget extends React.Component<Props> {
|
|
|
93
94
|
const backgroundImage = this.props.backgroundImage;
|
|
94
95
|
|
|
95
96
|
if (backgroundImage.url) {
|
|
97
|
+
const url = backgroundImage.url;
|
|
96
98
|
image = (
|
|
97
|
-
<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
<AssetContext.Consumer>
|
|
100
|
+
{({setAssetStatus}) => (
|
|
101
|
+
<SvgImage
|
|
102
|
+
src={url}
|
|
103
|
+
alt={
|
|
104
|
+
/* alt text is formatted in a sr-only
|
|
101
105
|
div next to the image in addition to
|
|
102
106
|
the alt attribute.
|
|
103
107
|
If there is no alt text at all,
|
|
@@ -117,22 +121,25 @@ class ImageWidget extends React.Component<Props> {
|
|
|
117
121
|
in practice right now, although
|
|
118
122
|
it will exhibit weird behaviour
|
|
119
123
|
while editing. */
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
124
|
+
this.props.alt
|
|
125
|
+
}
|
|
126
|
+
overrideAriaHidden={true}
|
|
127
|
+
width={backgroundImage.width}
|
|
128
|
+
height={backgroundImage.height}
|
|
129
|
+
preloader={apiOptions.imagePreloader}
|
|
130
|
+
extraGraphie={{
|
|
131
|
+
box: this.props.box,
|
|
132
|
+
range: this.props.range,
|
|
133
|
+
labels: this.props.labels,
|
|
134
|
+
}}
|
|
135
|
+
trackInteraction={this.props.trackInteraction}
|
|
136
|
+
zoomToFullSizeOnMobile={apiOptions.isMobile}
|
|
137
|
+
constrainHeight={apiOptions.isMobile}
|
|
138
|
+
allowFullBleed={apiOptions.isMobile}
|
|
139
|
+
setAssetStatus={setAssetStatus}
|
|
140
|
+
/>
|
|
141
|
+
)}
|
|
142
|
+
</AssetContext.Consumer>
|
|
136
143
|
);
|
|
137
144
|
}
|
|
138
145
|
|
|
@@ -22,12 +22,12 @@ import mediaQueries from "../styles/media-queries";
|
|
|
22
22
|
import AnswerChoices from "./label-image/answer-choices";
|
|
23
23
|
import Marker from "./label-image/marker";
|
|
24
24
|
|
|
25
|
-
import type {ChangeableProps} from "../mixins/changeable";
|
|
26
|
-
import type {APIOptions, PerseusScore, WidgetExports} from "../types";
|
|
27
25
|
import type {
|
|
28
26
|
InteractiveMarkerType,
|
|
29
27
|
InteractiveMarkerScore,
|
|
30
28
|
} from "./label-image/types";
|
|
29
|
+
import type {ChangeableProps} from "../mixins/changeable";
|
|
30
|
+
import type {APIOptions, PerseusScore, WidgetExports} from "../types";
|
|
31
31
|
|
|
32
32
|
type MarkersState = {
|
|
33
33
|
markers: ReadonlyArray<InteractiveMarkerType>;
|
|
@@ -7,6 +7,7 @@ import * as Changeable from "../mixins/changeable";
|
|
|
7
7
|
import {removeDenylistProps} from "../mixins/widget-prop-denylist";
|
|
8
8
|
import PerseusMarkdown from "../perseus-markdown";
|
|
9
9
|
|
|
10
|
+
import type {Passage, Reference} from "./passage";
|
|
10
11
|
import type {PerseusPassageRefWidgetOptions} from "../perseus-types";
|
|
11
12
|
import type {
|
|
12
13
|
ChangeFn,
|
|
@@ -14,7 +15,6 @@ import type {
|
|
|
14
15
|
WidgetExports,
|
|
15
16
|
WidgetProps,
|
|
16
17
|
} from "../types";
|
|
17
|
-
import type {Passage, Reference} from "./passage";
|
|
18
18
|
import type {LinterContextProps} from "@khanacademy/perseus-linter";
|
|
19
19
|
|
|
20
20
|
const EN_DASH = "\u2013";
|
package/src/widgets/passage.tsx
CHANGED
|
@@ -12,6 +12,7 @@ import Renderer from "../renderer";
|
|
|
12
12
|
|
|
13
13
|
import PassageMarkdown from "./passage/passage-markdown";
|
|
14
14
|
|
|
15
|
+
import type {ParseState} from "./passage/passage-markdown";
|
|
15
16
|
import type {SerializedHighlightSet} from "../components/highlighting/types";
|
|
16
17
|
import type {ChangeableProps} from "../mixins/changeable";
|
|
17
18
|
import type {PerseusPassageWidgetOptions} from "../perseus-types";
|
|
@@ -21,7 +22,6 @@ import type {
|
|
|
21
22
|
WidgetInfo,
|
|
22
23
|
WidgetProps,
|
|
23
24
|
} from "../types";
|
|
24
|
-
import type {ParseState} from "./passage/passage-markdown";
|
|
25
25
|
import type {SingleASTNode} from "@khanacademy/simple-markdown";
|
|
26
26
|
|
|
27
27
|
// A fake paragraph to measure the line height of the passage,
|
|
@@ -8,12 +8,12 @@ import PassageRef from "../passage-ref";
|
|
|
8
8
|
|
|
9
9
|
import BaseRadio from "./base-radio";
|
|
10
10
|
|
|
11
|
+
import type {FocusFunction, ChoiceType} from "./base-radio";
|
|
11
12
|
import type {
|
|
12
13
|
PerseusRadioChoice,
|
|
13
14
|
PerseusRadioWidgetOptions,
|
|
14
15
|
} from "../../perseus-types";
|
|
15
16
|
import type {PerseusScore, WidgetProps, ChoiceState} from "../../types";
|
|
16
|
-
import type {FocusFunction, ChoiceType} from "./base-radio";
|
|
17
17
|
|
|
18
18
|
// RenderProps is the return type for radio.jsx#transform
|
|
19
19
|
export type RenderProps = {
|