@khanacademy/perseus 17.0.0 → 17.1.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 +59 -0
- package/dist/dependencies.d.ts +1 -0
- package/dist/es/index.css +20 -0
- package/dist/es/index.js +443 -338
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +20 -0
- package/dist/index.js +444 -340
- package/dist/index.js.map +1 -1
- package/dist/perseus-types.d.ts +1 -0
- package/dist/strings.js +1 -0
- package/dist/widgets/label-image/answer-pill.d.ts +10 -0
- package/dist/widgets/label-image/hide-answers-toggle.d.ts +6 -0
- package/dist/widgets/label-image/marker.d.ts +6 -9
- package/dist/widgets/label-image/types.d.ts +1 -0
- package/dist/widgets/label-image.d.ts +101 -12
- package/package.json +30 -22
- package/src/__tests__/__snapshots__/renderer.test.tsx.snap +3 -3
- package/src/dependencies.ts +4 -0
- package/src/perseus-types.ts +2 -0
- package/src/styles/widgets/label-image.less +32 -0
- package/src/widgets/__stories__/label-image.stories.tsx +73 -3
- package/src/widgets/__testdata__/label-image.testdata.ts +118 -1
- package/src/widgets/__tests__/__snapshots__/dropdown.test.ts.snap +2 -2
- package/src/widgets/label-image/__stories__/answer-pill.stories.tsx +62 -0
- package/src/widgets/label-image/__tests__/answer-pill.test.tsx +83 -0
- package/src/widgets/label-image/__tests__/hide-answers-toggle.test.tsx +39 -0
- package/src/widgets/label-image/answer-choices.tsx +7 -1
- package/src/widgets/label-image/answer-pill.tsx +89 -0
- package/src/widgets/label-image/hide-answers-toggle.tsx +40 -0
- package/src/widgets/label-image/marker.tsx +116 -174
- package/src/widgets/label-image/types.ts +1 -0
- package/src/widgets/label-image.test.ts +142 -52
- package/src/widgets/label-image.tsx +212 -135
- package/tsconfig-build.tsbuildinfo +1 -1
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import Color from "@khanacademy/wonder-blocks-color";
|
|
9
|
+
import {View, type StyleType} from "@khanacademy/wonder-blocks-core";
|
|
9
10
|
import {StyleSheet, css} from "aphrodite";
|
|
10
11
|
import * as React from "react";
|
|
11
12
|
|
|
12
13
|
import Icon from "../../components/icon";
|
|
13
|
-
import {iconCheck, iconMinus} from "../../icon-paths";
|
|
14
|
+
import {iconCheck, iconChevronDown, iconMinus} from "../../icon-paths";
|
|
14
15
|
|
|
15
16
|
import type {InteractiveMarkerType} from "./types";
|
|
16
17
|
|
|
@@ -20,16 +21,19 @@ type Props = InteractiveMarkerType & {
|
|
|
20
21
|
// Whether this marker should pulsate to draw user attention.
|
|
21
22
|
showPulsate: boolean;
|
|
22
23
|
// Callbacks for when marker is interacted with using input device.
|
|
23
|
-
onClick: (e: MouseEvent) => void;
|
|
24
|
-
onKeyDown: (e: KeyboardEvent) => void;
|
|
24
|
+
onClick: (e: React.MouseEvent) => void;
|
|
25
|
+
onKeyDown: (e: React.KeyboardEvent) => void;
|
|
26
|
+
onFocus: () => void;
|
|
27
|
+
onBlur: () => void;
|
|
28
|
+
focused: boolean;
|
|
25
29
|
};
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
+
function shouldReduceMotion(): boolean {
|
|
32
|
+
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
33
|
+
return !mediaQuery || mediaQuery.matches;
|
|
34
|
+
}
|
|
31
35
|
|
|
32
|
-
export default class Marker extends React.Component<Props
|
|
36
|
+
export default class Marker extends React.Component<Props> {
|
|
33
37
|
// The marker icon element.
|
|
34
38
|
_icon: HTMLElement | null | undefined;
|
|
35
39
|
|
|
@@ -39,34 +43,20 @@ export default class Marker extends React.Component<Props, State> {
|
|
|
39
43
|
selected: [],
|
|
40
44
|
};
|
|
41
45
|
|
|
42
|
-
state: State = {
|
|
43
|
-
isFocused: false,
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
handleFocus() {
|
|
47
|
-
this.setState({isFocused: true});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
handleBlur() {
|
|
51
|
-
this.setState({isFocused: false});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
46
|
renderIcon(): React.ReactElement<React.ComponentProps<"div">> {
|
|
55
|
-
const {selected, showCorrectness, showSelected, showPulsate} =
|
|
47
|
+
const {selected, showCorrectness, showSelected, showPulsate, focused} =
|
|
56
48
|
this.props;
|
|
57
49
|
|
|
58
|
-
const {isFocused} = this.state;
|
|
59
|
-
|
|
60
50
|
// Only a single marker may be "selected" at a time.
|
|
61
51
|
// `showSelected` is a controlled prop, that may be set to `true` for
|
|
62
52
|
// one marker at a time.
|
|
63
|
-
// `
|
|
53
|
+
// `focused` is a controlled state, driven by focus events, and may
|
|
64
54
|
// only be `true` when there's no answer choices popup visible, and
|
|
65
55
|
// keyboard focus is given to the marker.
|
|
66
|
-
const isSelected = showSelected ||
|
|
56
|
+
const isSelected = showSelected || focused;
|
|
67
57
|
|
|
68
|
-
let innerIcon;
|
|
69
|
-
let iconStyles;
|
|
58
|
+
let innerIcon: React.ReactElement | undefined;
|
|
59
|
+
let iconStyles: StyleType;
|
|
70
60
|
|
|
71
61
|
if (showCorrectness) {
|
|
72
62
|
innerIcon = (
|
|
@@ -79,35 +69,52 @@ export default class Marker extends React.Component<Props, State> {
|
|
|
79
69
|
|
|
80
70
|
if (showCorrectness === "correct") {
|
|
81
71
|
iconStyles = [
|
|
72
|
+
styles.markerGraded,
|
|
82
73
|
styles.markerCorrect,
|
|
83
|
-
isSelected && styles.
|
|
74
|
+
isSelected && styles.markerSelected,
|
|
84
75
|
];
|
|
85
76
|
} else {
|
|
86
77
|
iconStyles = [
|
|
78
|
+
styles.markerGraded,
|
|
87
79
|
styles.markerIncorrect,
|
|
88
|
-
isSelected && styles.
|
|
80
|
+
isSelected && styles.markerSelected,
|
|
89
81
|
];
|
|
90
82
|
}
|
|
83
|
+
} else if (focused) {
|
|
84
|
+
iconStyles = [
|
|
85
|
+
styles.markerFocused,
|
|
86
|
+
selected && selected.length > 0 && styles.markerFilled,
|
|
87
|
+
showSelected && styles.markerSelected,
|
|
88
|
+
];
|
|
91
89
|
} else if (selected && selected.length > 0) {
|
|
92
90
|
iconStyles = [
|
|
93
91
|
styles.markerFilled,
|
|
94
|
-
isSelected && styles.
|
|
92
|
+
isSelected && styles.markerSelected,
|
|
95
93
|
];
|
|
94
|
+
} else if (isSelected) {
|
|
95
|
+
iconStyles = [styles.markerSelected];
|
|
96
96
|
} else {
|
|
97
97
|
iconStyles = [
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
styles.markerPulsateBase,
|
|
99
|
+
shouldReduceMotion()
|
|
100
|
+
? showPulsate && styles.markerUnfilledPulsateOnce
|
|
101
|
+
: showPulsate && styles.markerUnfilledPulsateInfinite,
|
|
101
102
|
];
|
|
102
103
|
}
|
|
103
104
|
|
|
105
|
+
if (isSelected && selected && selected.length === 0) {
|
|
106
|
+
innerIcon = (
|
|
107
|
+
<Icon icon={iconChevronDown} size={8} color={Color.white} />
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
104
111
|
return (
|
|
105
|
-
<
|
|
106
|
-
|
|
112
|
+
<View
|
|
113
|
+
style={[styles.markerIcon, iconStyles]}
|
|
107
114
|
ref={(node) => (this._icon = node)}
|
|
108
115
|
>
|
|
109
116
|
{innerIcon}
|
|
110
|
-
</
|
|
117
|
+
</View>
|
|
111
118
|
);
|
|
112
119
|
}
|
|
113
120
|
|
|
@@ -131,11 +138,12 @@ export default class Marker extends React.Component<Props, State> {
|
|
|
131
138
|
top: `${y}%`,
|
|
132
139
|
}}
|
|
133
140
|
tabIndex={isDisabled ? -1 : 0}
|
|
134
|
-
onFocus={
|
|
135
|
-
onBlur={
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
141
|
+
onFocus={this.props.onFocus}
|
|
142
|
+
onBlur={this.props.onBlur}
|
|
143
|
+
onClick={(e) => {
|
|
144
|
+
e.preventDefault();
|
|
145
|
+
this.props.onClick(e);
|
|
146
|
+
}}
|
|
139
147
|
onKeyDown={(e) => this.props.onKeyDown(e)}
|
|
140
148
|
>
|
|
141
149
|
{this.renderIcon()}
|
|
@@ -144,16 +152,10 @@ export default class Marker extends React.Component<Props, State> {
|
|
|
144
152
|
}
|
|
145
153
|
}
|
|
146
154
|
|
|
147
|
-
const markerColor = "#1865f2";
|
|
148
|
-
const selectedColor = "#2552b0";
|
|
149
|
-
const activeColor = selectedColor;
|
|
150
|
-
const correctColor = "#00a60e";
|
|
151
|
-
const correctActiveColor = "#167b1f";
|
|
152
|
-
const incorrectColor = "#909195";
|
|
153
|
-
const incorrectActiveColor = "#6c6e73";
|
|
154
|
-
const markerShadowColor = "rgba(33, 36, 44, 0.32)";
|
|
155
155
|
const lightShadowColor = "rgba(33, 36, 44, 0.16)";
|
|
156
156
|
|
|
157
|
+
const markerSize = 20;
|
|
158
|
+
|
|
157
159
|
const styles = StyleSheet.create({
|
|
158
160
|
unstyledButton: {
|
|
159
161
|
padding: 0,
|
|
@@ -171,10 +173,13 @@ const styles = StyleSheet.create({
|
|
|
171
173
|
|
|
172
174
|
marker: {
|
|
173
175
|
position: "absolute",
|
|
176
|
+
outline: "none",
|
|
174
177
|
|
|
175
178
|
// Center marker position based on it's maximum size.
|
|
176
|
-
width:
|
|
177
|
-
height:
|
|
179
|
+
width: markerSize,
|
|
180
|
+
height: markerSize,
|
|
181
|
+
backgroundColor: Color.white,
|
|
182
|
+
borderRadius: 30,
|
|
178
183
|
marginLeft: -15,
|
|
179
184
|
marginTop: -15,
|
|
180
185
|
},
|
|
@@ -186,180 +191,117 @@ const styles = StyleSheet.create({
|
|
|
186
191
|
// The base and unfilled marker style.
|
|
187
192
|
markerIcon: {
|
|
188
193
|
display: "flex",
|
|
189
|
-
|
|
194
|
+
alignItems: "center",
|
|
195
|
+
justifyContent: "center",
|
|
190
196
|
|
|
191
197
|
boxSizing: "content-box",
|
|
192
198
|
|
|
193
|
-
width:
|
|
194
|
-
height:
|
|
195
|
-
|
|
196
|
-
marginLeft:
|
|
199
|
+
width: markerSize,
|
|
200
|
+
height: markerSize,
|
|
201
|
+
|
|
202
|
+
marginLeft: 2,
|
|
197
203
|
|
|
198
204
|
cursor: "pointer",
|
|
199
205
|
|
|
200
|
-
|
|
206
|
+
border: `2px solid ${Color.offBlack64}`,
|
|
207
|
+
borderRadius: markerSize,
|
|
201
208
|
|
|
202
|
-
|
|
203
|
-
borderRadius: 16,
|
|
209
|
+
boxShadow: `0 8px 8px ${Color.offBlack8}`,
|
|
204
210
|
|
|
205
|
-
|
|
211
|
+
":hover": {
|
|
212
|
+
outline: `2px solid ${Color.blue}`,
|
|
213
|
+
outlineOffset: 2,
|
|
214
|
+
},
|
|
206
215
|
},
|
|
207
216
|
|
|
208
|
-
|
|
217
|
+
// The animation that presents the marker to the learner
|
|
218
|
+
markerPulsateBase: {
|
|
209
219
|
animationName: {
|
|
210
220
|
"0%": {
|
|
211
221
|
transform: "scale(1)",
|
|
222
|
+
backgroundColor: Color.blue,
|
|
212
223
|
},
|
|
213
224
|
|
|
214
225
|
"100%": {
|
|
215
|
-
transform: "scale(1.
|
|
226
|
+
transform: "scale(1.3)",
|
|
227
|
+
backgroundColor: Color.blue,
|
|
216
228
|
},
|
|
217
229
|
},
|
|
218
230
|
|
|
219
231
|
animationDirection: "alternate",
|
|
220
232
|
animationDuration: "0.8s",
|
|
221
|
-
animationIterationCount: "infinite",
|
|
222
233
|
animationTimingFunction: "ease-in",
|
|
223
234
|
|
|
224
235
|
transformOrigin: "50% 50%",
|
|
225
|
-
},
|
|
226
|
-
|
|
227
|
-
markerUnfilledSelected: {
|
|
228
|
-
"::before": {
|
|
229
|
-
content: "''",
|
|
230
|
-
display: "inline-block",
|
|
231
|
-
position: "absolute",
|
|
232
|
-
|
|
233
|
-
width: 20,
|
|
234
|
-
height: 20,
|
|
235
|
-
marginLeft: -4,
|
|
236
|
-
marginTop: -4,
|
|
237
|
-
|
|
238
|
-
border: `solid 2px ${selectedColor}`,
|
|
239
|
-
borderRadius: 20,
|
|
240
|
-
},
|
|
241
236
|
|
|
242
|
-
|
|
243
|
-
|
|
237
|
+
animationIterationCount: "0",
|
|
238
|
+
},
|
|
244
239
|
|
|
245
|
-
|
|
240
|
+
markerUnfilledPulsateInfinite: {
|
|
241
|
+
animationIterationCount: "infinite",
|
|
246
242
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
},
|
|
243
|
+
":hover": {
|
|
244
|
+
outline: `0px solid ${Color.blue}`,
|
|
250
245
|
},
|
|
251
246
|
},
|
|
252
247
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
// Center icon within marker.
|
|
257
|
-
marginLeft: 9,
|
|
258
|
-
|
|
259
|
-
borderRadius: 8,
|
|
260
|
-
|
|
261
|
-
boxShadow: `0 1px 1px 0 ${lightShadowColor}`,
|
|
248
|
+
markerUnfilledPulsateOnce: {
|
|
249
|
+
// Doing the animation twice lets it ease-in and ease-out
|
|
250
|
+
animationIterationCount: "2",
|
|
262
251
|
},
|
|
263
252
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
position: "absolute",
|
|
269
|
-
|
|
270
|
-
width: 12,
|
|
271
|
-
height: 12,
|
|
272
|
-
marginLeft: -4,
|
|
273
|
-
marginTop: -4,
|
|
274
|
-
|
|
275
|
-
border: `solid 2px ${selectedColor}`,
|
|
276
|
-
borderRadius: 12,
|
|
277
|
-
},
|
|
253
|
+
markerFocused: {
|
|
254
|
+
outline: `2px solid ${Color.blue}`,
|
|
255
|
+
outlineOffset: 2,
|
|
256
|
+
},
|
|
278
257
|
|
|
279
|
-
|
|
280
|
-
|
|
258
|
+
// The learner is making an initial selection
|
|
259
|
+
markerSelected: {
|
|
260
|
+
boxShadow: `0 8px 8px ${Color.offBlack8}`,
|
|
281
261
|
|
|
282
|
-
|
|
262
|
+
border: `solid 4px ${Color.white}`,
|
|
263
|
+
backgroundColor: Color.blue,
|
|
264
|
+
borderRadius: markerSize,
|
|
265
|
+
transform: "rotate(180deg)",
|
|
283
266
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
267
|
+
":hover": {
|
|
268
|
+
outline: `2px solid ${Color.blue}`,
|
|
269
|
+
outlineOffset: -2,
|
|
287
270
|
},
|
|
288
271
|
},
|
|
289
272
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
273
|
+
// The learner has made a selection
|
|
274
|
+
markerFilled: {
|
|
275
|
+
width: markerSize - 2,
|
|
276
|
+
height: markerSize - 2,
|
|
277
|
+
borderRadius: markerSize - 2,
|
|
278
|
+
backgroundColor: "#ECF3FE",
|
|
295
279
|
|
|
296
|
-
|
|
297
|
-
|
|
280
|
+
marginLeft: 3,
|
|
281
|
+
marginTop: 0,
|
|
298
282
|
|
|
299
|
-
|
|
283
|
+
border: `4px solid ${Color.blue}`,
|
|
300
284
|
|
|
301
285
|
boxShadow: `0 1px 1px 0 ${lightShadowColor}`,
|
|
302
286
|
},
|
|
303
287
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
display: "inline-block",
|
|
308
|
-
position: "absolute",
|
|
309
|
-
|
|
310
|
-
width: 28,
|
|
311
|
-
height: 28,
|
|
312
|
-
|
|
313
|
-
border: `solid 2px ${selectedColor}`,
|
|
314
|
-
borderRadius: 28,
|
|
315
|
-
},
|
|
316
|
-
|
|
317
|
-
":active": {
|
|
318
|
-
backgroundColor: correctActiveColor,
|
|
319
|
-
|
|
320
|
-
boxShadow: "none",
|
|
321
|
-
|
|
322
|
-
"::before": {
|
|
323
|
-
display: "none",
|
|
324
|
-
},
|
|
325
|
-
},
|
|
326
|
-
},
|
|
327
|
-
|
|
328
|
-
markerIncorrect: {
|
|
329
|
-
width: 24,
|
|
330
|
-
height: 24,
|
|
288
|
+
markerGraded: {
|
|
289
|
+
width: markerSize,
|
|
290
|
+
height: markerSize,
|
|
331
291
|
marginLeft: 1,
|
|
332
292
|
marginTop: 1,
|
|
333
293
|
|
|
334
294
|
justifyContent: "center",
|
|
335
295
|
alignItems: "center",
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
boxShadow: `0 1px 1px 0 ${lightShadowColor}`,
|
|
296
|
+
border: `2px solid ${Color.white}`,
|
|
297
|
+
boxShadow: `0 8px 8px ${Color.offBlack8}`,
|
|
340
298
|
},
|
|
341
299
|
|
|
342
|
-
|
|
343
|
-
"
|
|
344
|
-
|
|
345
|
-
display: "inline-block",
|
|
346
|
-
position: "absolute",
|
|
347
|
-
|
|
348
|
-
width: 28,
|
|
349
|
-
height: 28,
|
|
350
|
-
|
|
351
|
-
border: `solid 2px ${selectedColor}`,
|
|
352
|
-
borderRadius: 28,
|
|
353
|
-
},
|
|
354
|
-
|
|
355
|
-
":active": {
|
|
356
|
-
backgroundColor: incorrectActiveColor,
|
|
357
|
-
|
|
358
|
-
boxShadow: "none",
|
|
300
|
+
markerCorrect: {
|
|
301
|
+
background: "#00880b", // WB green darkened by 18%
|
|
302
|
+
},
|
|
359
303
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
},
|
|
363
|
-
},
|
|
304
|
+
markerIncorrect: {
|
|
305
|
+
background: Color.offBlack64,
|
|
364
306
|
},
|
|
365
307
|
});
|
|
@@ -15,6 +15,7 @@ export type InteractiveMarkerType = MarkerType & {
|
|
|
15
15
|
selected?: ReadonlyArray<string>;
|
|
16
16
|
// Reveal the correctness state of the user selected answers for the marker.
|
|
17
17
|
showCorrectness?: "correct" | "incorrect";
|
|
18
|
+
focused?: boolean;
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
// Question state for marker as result of user selected answers.
|
|
@@ -1,6 +1,17 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {screen} from "@testing-library/react";
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
import {
|
|
5
|
+
testDependencies,
|
|
6
|
+
testDependenciesV2,
|
|
7
|
+
} from "../../../../testing/test-dependencies";
|
|
8
|
+
import * as Dependencies from "../dependencies";
|
|
9
|
+
|
|
10
|
+
import {textQuestion} from "./__testdata__/label-image.testdata";
|
|
11
|
+
import {renderQuestion} from "./__tests__/renderQuestion";
|
|
12
|
+
import {LabelImage} from "./label-image";
|
|
13
|
+
|
|
14
|
+
import "@testing-library/jest-dom";
|
|
4
15
|
|
|
5
16
|
const emptyMarker = {
|
|
6
17
|
label: "",
|
|
@@ -236,67 +247,75 @@ describe("LabelImage", function () {
|
|
|
236
247
|
|
|
237
248
|
describe("imageSideForMarkerPosition", function () {
|
|
238
249
|
it("should return side: top", function () {
|
|
239
|
-
expect(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
expect(
|
|
243
|
-
"
|
|
244
|
-
);
|
|
245
|
-
expect(
|
|
246
|
-
"
|
|
247
|
-
);
|
|
250
|
+
expect(
|
|
251
|
+
LabelImage.imageSideForMarkerPosition(21, 0, "NONE"),
|
|
252
|
+
).toEqual("top");
|
|
253
|
+
expect(
|
|
254
|
+
LabelImage.imageSideForMarkerPosition(79, 0, "NONE"),
|
|
255
|
+
).toEqual("top");
|
|
256
|
+
expect(
|
|
257
|
+
LabelImage.imageSideForMarkerPosition(50, 0, "NONE"),
|
|
258
|
+
).toEqual("top");
|
|
259
|
+
expect(
|
|
260
|
+
LabelImage.imageSideForMarkerPosition(50, 25, "NONE"),
|
|
261
|
+
).toEqual("top");
|
|
262
|
+
expect(
|
|
263
|
+
LabelImage.imageSideForMarkerPosition(50, 50, "NONE"),
|
|
264
|
+
).toEqual("top");
|
|
248
265
|
});
|
|
249
266
|
|
|
250
267
|
it("should return side: right", function () {
|
|
251
|
-
expect(
|
|
252
|
-
"
|
|
253
|
-
);
|
|
254
|
-
expect(
|
|
255
|
-
"
|
|
256
|
-
);
|
|
257
|
-
expect(
|
|
258
|
-
"
|
|
259
|
-
);
|
|
260
|
-
expect(
|
|
261
|
-
"
|
|
262
|
-
);
|
|
263
|
-
expect(
|
|
264
|
-
"
|
|
265
|
-
);
|
|
268
|
+
expect(
|
|
269
|
+
LabelImage.imageSideForMarkerPosition(100, 0, "NONE"),
|
|
270
|
+
).toEqual("right");
|
|
271
|
+
expect(
|
|
272
|
+
LabelImage.imageSideForMarkerPosition(100, 100, "NONE"),
|
|
273
|
+
).toEqual("right");
|
|
274
|
+
expect(
|
|
275
|
+
LabelImage.imageSideForMarkerPosition(100, 50, "NONE"),
|
|
276
|
+
).toEqual("right");
|
|
277
|
+
expect(
|
|
278
|
+
LabelImage.imageSideForMarkerPosition(75, 50, "NONE"),
|
|
279
|
+
).toEqual("right");
|
|
280
|
+
expect(
|
|
281
|
+
LabelImage.imageSideForMarkerPosition(51, 50, "NONE"),
|
|
282
|
+
).toEqual("right");
|
|
266
283
|
});
|
|
267
284
|
|
|
268
285
|
it("should return side: bottom", function () {
|
|
269
|
-
expect(
|
|
270
|
-
"
|
|
271
|
-
);
|
|
272
|
-
expect(
|
|
273
|
-
"
|
|
274
|
-
);
|
|
275
|
-
expect(
|
|
276
|
-
"
|
|
277
|
-
);
|
|
278
|
-
expect(
|
|
279
|
-
"
|
|
280
|
-
);
|
|
281
|
-
expect(
|
|
282
|
-
"
|
|
283
|
-
);
|
|
286
|
+
expect(
|
|
287
|
+
LabelImage.imageSideForMarkerPosition(21, 100, "NONE"),
|
|
288
|
+
).toEqual("bottom");
|
|
289
|
+
expect(
|
|
290
|
+
LabelImage.imageSideForMarkerPosition(79, 100, "NONE"),
|
|
291
|
+
).toEqual("bottom");
|
|
292
|
+
expect(
|
|
293
|
+
LabelImage.imageSideForMarkerPosition(50, 100, "NONE"),
|
|
294
|
+
).toEqual("bottom");
|
|
295
|
+
expect(
|
|
296
|
+
LabelImage.imageSideForMarkerPosition(50, 75, "NONE"),
|
|
297
|
+
).toEqual("bottom");
|
|
298
|
+
expect(
|
|
299
|
+
LabelImage.imageSideForMarkerPosition(50, 51, "NONE"),
|
|
300
|
+
).toEqual("bottom");
|
|
284
301
|
});
|
|
285
302
|
|
|
286
303
|
it("should return side: left", function () {
|
|
287
|
-
expect(LabelImage.imageSideForMarkerPosition(0, 0)).toEqual(
|
|
288
|
-
expect(LabelImage.imageSideForMarkerPosition(0, 100)).toEqual(
|
|
289
|
-
"left",
|
|
290
|
-
);
|
|
291
|
-
expect(LabelImage.imageSideForMarkerPosition(0, 50)).toEqual(
|
|
292
|
-
"left",
|
|
293
|
-
);
|
|
294
|
-
expect(LabelImage.imageSideForMarkerPosition(25, 50)).toEqual(
|
|
295
|
-
"left",
|
|
296
|
-
);
|
|
297
|
-
expect(LabelImage.imageSideForMarkerPosition(49, 50)).toEqual(
|
|
304
|
+
expect(LabelImage.imageSideForMarkerPosition(0, 0, "NONE")).toEqual(
|
|
298
305
|
"left",
|
|
299
306
|
);
|
|
307
|
+
expect(
|
|
308
|
+
LabelImage.imageSideForMarkerPosition(0, 100, "NONE"),
|
|
309
|
+
).toEqual("left");
|
|
310
|
+
expect(
|
|
311
|
+
LabelImage.imageSideForMarkerPosition(0, 50, "NONE"),
|
|
312
|
+
).toEqual("left");
|
|
313
|
+
expect(
|
|
314
|
+
LabelImage.imageSideForMarkerPosition(25, 50, "NONE"),
|
|
315
|
+
).toEqual("left");
|
|
316
|
+
expect(
|
|
317
|
+
LabelImage.imageSideForMarkerPosition(49, 50, "NONE"),
|
|
318
|
+
).toEqual("left");
|
|
300
319
|
});
|
|
301
320
|
});
|
|
302
321
|
|
|
@@ -837,4 +856,75 @@ describe("LabelImage", function () {
|
|
|
837
856
|
});
|
|
838
857
|
});
|
|
839
858
|
});
|
|
859
|
+
|
|
860
|
+
describe("analytics", () => {
|
|
861
|
+
beforeEach(() => {
|
|
862
|
+
jest.spyOn(testDependenciesV2.analytics, "onAnalyticsEvent");
|
|
863
|
+
jest.spyOn(Dependencies, "getDependencies").mockReturnValue(
|
|
864
|
+
testDependencies,
|
|
865
|
+
);
|
|
866
|
+
jest.spyOn(Dependencies, "useDependencies").mockReturnValue(
|
|
867
|
+
testDependenciesV2,
|
|
868
|
+
);
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
it("sends an analytics event when the toggle is interacted with", async () => {
|
|
872
|
+
// render component
|
|
873
|
+
renderQuestion(textQuestion);
|
|
874
|
+
|
|
875
|
+
// Toggle the switch
|
|
876
|
+
const toggleAnswerSwitch = await screen.findByRole("switch");
|
|
877
|
+
userEvent.click(toggleAnswerSwitch);
|
|
878
|
+
|
|
879
|
+
expect(
|
|
880
|
+
testDependenciesV2.analytics.onAnalyticsEvent,
|
|
881
|
+
).toHaveBeenCalledWith({
|
|
882
|
+
type: "perseus:label-image:toggle-answers-hidden",
|
|
883
|
+
payload: null,
|
|
884
|
+
});
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
it("sends an analytics event when a marker is interacted with", async () => {
|
|
888
|
+
// render component
|
|
889
|
+
renderQuestion(textQuestion);
|
|
890
|
+
|
|
891
|
+
// Toggle the button
|
|
892
|
+
const markerButton = await screen.findByLabelText(
|
|
893
|
+
"The fourth unlabeled bar line.",
|
|
894
|
+
);
|
|
895
|
+
userEvent.click(markerButton);
|
|
896
|
+
|
|
897
|
+
expect(
|
|
898
|
+
testDependenciesV2.analytics.onAnalyticsEvent,
|
|
899
|
+
).toHaveBeenCalledWith({
|
|
900
|
+
type: "perseus:label-image:marker-interacted-with",
|
|
901
|
+
payload: null,
|
|
902
|
+
});
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
it("sends analytics when a choice is interacted with", async () => {
|
|
906
|
+
// render component
|
|
907
|
+
renderQuestion(textQuestion);
|
|
908
|
+
|
|
909
|
+
// Toggle the marker
|
|
910
|
+
const markerButton = await screen.findByLabelText(
|
|
911
|
+
"The fourth unlabeled bar line.",
|
|
912
|
+
);
|
|
913
|
+
userEvent.click(markerButton);
|
|
914
|
+
|
|
915
|
+
await screen.findByRole("dialog");
|
|
916
|
+
|
|
917
|
+
// Select a choice
|
|
918
|
+
const choices = await screen.findAllByText("Trucks");
|
|
919
|
+
const choice = choices[choices.length - 1];
|
|
920
|
+
userEvent.click(choice);
|
|
921
|
+
|
|
922
|
+
expect(
|
|
923
|
+
testDependenciesV2.analytics.onAnalyticsEvent,
|
|
924
|
+
).toHaveBeenCalledWith({
|
|
925
|
+
type: "perseus:label-image:choiced-interacted-with",
|
|
926
|
+
payload: null,
|
|
927
|
+
});
|
|
928
|
+
});
|
|
929
|
+
});
|
|
840
930
|
});
|