@khanacademy/perseus 11.6.1 → 12.0.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.
@@ -1,41 +1,79 @@
1
+ /* eslint-disable @khanacademy/ts-no-error-suppressions */
1
2
  import {
3
+ DesktopKeypad,
2
4
  keyTranslator,
3
5
  createMathField,
4
6
  mathQuillInstance,
7
+ CursorContext,
8
+ getCursorContext,
5
9
  } from "@khanacademy/math-input";
10
+ import Clickable from "@khanacademy/wonder-blocks-clickable";
11
+ import Color, {fade} from "@khanacademy/wonder-blocks-color";
12
+ import {View} from "@khanacademy/wonder-blocks-core";
13
+ import * as i18n from "@khanacademy/wonder-blocks-i18n";
14
+ import {Popover, PopoverContentCore} from "@khanacademy/wonder-blocks-popover";
15
+ import Spacing from "@khanacademy/wonder-blocks-spacing";
16
+ import {StyleSheet} from "aphrodite";
6
17
  import classNames from "classnames";
7
18
  import $ from "jquery";
8
19
  import * as React from "react";
9
- import ReactDOM from "react-dom";
10
20
  import _ from "underscore";
11
21
 
12
- import TexButtons from "./tex-buttons";
13
-
14
- import type {ButtonSetsType} from "./tex-buttons";
15
- import type {MathFieldInterface} from "@khanacademy/math-input";
22
+ import type {LegacyButtonSets} from "../perseus-types";
23
+ import type {PerseusDependenciesV2} from "../types";
24
+ import type {Keys, MathFieldInterface} from "@khanacademy/math-input";
16
25
 
17
26
  type ButtonsVisibleType = "always" | "never" | "focused";
18
27
 
28
+ type KeypadButtonSets = {
29
+ advancedRelations?: boolean;
30
+ basicRelations?: boolean;
31
+ divisionKey?: boolean;
32
+ logarithms?: boolean;
33
+ preAlgebra?: boolean;
34
+ trigonometry?: boolean;
35
+ };
36
+
19
37
  type Props = {
20
38
  className?: string;
21
39
  value: string;
22
40
  onChange: any;
23
41
  convertDotToTimes: boolean;
24
- buttonsVisible: ButtonsVisibleType;
25
- buttonSets: ButtonSetsType;
42
+ /**
43
+ * @deprecated Use `keypadButtonSets` instead. Maps to `keypadButtonSets`.
44
+ * @see keypadButtonSets
45
+ */
46
+ buttonSets?: LegacyButtonSets;
47
+ /**
48
+ * Overrides deprecated `buttonSets` prop.
49
+ */
50
+ keypadButtonSets?: KeypadButtonSets;
26
51
  labelText?: string;
27
52
  onFocus?: () => void;
28
53
  onBlur?: () => void;
54
+ hasError?: boolean;
55
+ extraKeys?: ReadonlyArray<Keys>;
56
+ /**
57
+ * Whether to show the keypad buttons.
58
+ * The strings now misleading, but we keep them for backwards compatibility.
59
+ * - `focused` means that the keypad **appears on toggle, *off* by default**.
60
+ * - `always` means that the keypad **appears on toggle, *on* by default.**
61
+ * - `never` means that the keypad is **never shown**.
62
+ */
63
+ buttonsVisible?: ButtonsVisibleType;
64
+ analytics: PerseusDependenciesV2["analytics"];
29
65
  };
30
66
 
31
67
  type DefaultProps = {
32
68
  value: Props["value"];
33
69
  convertDotToTimes: Props["convertDotToTimes"];
34
- buttonsVisible: Props["buttonsVisible"];
35
70
  };
36
71
 
37
72
  type State = {
38
73
  focused: boolean;
74
+ keypadOpen: boolean;
75
+ cursorContext: typeof CursorContext[keyof typeof CursorContext];
76
+ openedWithEventType?: string;
39
77
  };
40
78
 
41
79
  const customKeyTranslator = {
@@ -62,81 +100,30 @@ class MathInput extends React.Component<Props, State> {
62
100
  static defaultProps: DefaultProps = {
63
101
  value: "",
64
102
  convertDotToTimes: false,
65
- buttonsVisible: "focused",
66
103
  };
67
104
 
68
- state: State = {focused: false};
105
+ state: State = {
106
+ focused: false,
107
+ keypadOpen: this.props.buttonsVisible === "always" ? true : false,
108
+ cursorContext: CursorContext.NONE,
109
+ };
69
110
 
70
111
  componentDidMount() {
71
- window.addEventListener("mousedown", this.handleMouseDown);
72
- window.addEventListener("mouseup", this.handleMouseUp);
73
-
74
112
  // Ideally, we would be able to pass an initial value directly into
75
113
  // the constructor
76
114
  this.mathField()?.latex(this.props.value);
77
115
  }
78
116
 
79
- componentWillUnmount() {
80
- window.removeEventListener("mousedown", this.handleMouseDown);
81
- window.removeEventListener("mouseup", this.handleMouseUp);
82
- }
83
-
84
- // handlers:
85
- // keep track of two related bits of state:
86
- // * this.state.focused - whether the buttons are currently shown
87
- // * this.mouseDown - whether a mouse click is active that started in the
88
- // buttons div
89
-
90
- handleFocus: () => void = () => {
91
- this.setState({focused: true});
92
- // TODO(joel) fix properly - we should probably allow onFocus handlers
93
- // to this property, but we need to work correctly with them.
94
- // if (this.props.onFocus) {
95
- // this.props.onFocus();
96
- // }
97
- };
98
-
99
- handleMouseDown: (arg1: MouseEvent) => void = (event) => {
100
- // @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2345 - Argument of type 'EventTarget | null' is not assignable to parameter of type 'Node | null'.
101
- const focused = ReactDOM.findDOMNode(this).contains(event.target);
102
- this.mouseDown = focused;
103
- if (!focused) {
104
- this.setState({focused: false});
105
- }
106
- };
107
-
108
- handleMouseUp: (arg1: MouseEvent) => void = () => {
109
- // this mouse click started in the buttons div so we should focus the
110
- // input
111
- if (this.mouseDown) {
112
- this.focus();
113
- }
114
- this.mouseDown = false;
115
- };
116
-
117
- handleBlur: (arg1: React.FocusEvent) => void = (e) => {
118
- // TODO(michaelpolyak): Consider trapping focus within the button group.
119
- // Focusing back on the input when TAB out of the last button in the
120
- // group. This will probably require ESCAPE key handling to enable to
121
- // close (blur) the button group in order to focus on next page element.
122
- if (
123
- !this.mouseDown &&
124
- // @ts-expect-error - TS2531 - Object is possibly 'null'.
125
- !ReactDOM.findDOMNode(this).contains(e.relatedTarget)
126
- ) {
127
- this.setState({focused: false});
128
- }
129
- };
130
-
131
- _shouldShowButtons: () => boolean = () => {
132
- if (this.props.buttonsVisible === "always") {
133
- return true;
134
- }
117
+ openKeypad() {
135
118
  if (this.props.buttonsVisible === "never") {
136
- return false;
119
+ return;
137
120
  }
138
- return this.state.focused;
139
- };
121
+ this.setState({keypadOpen: true});
122
+ }
123
+
124
+ closeKeypad() {
125
+ this.setState({keypadOpen: false});
126
+ }
140
127
 
141
128
  insert: (value: any) => void = (value) => {
142
129
  const input = this.mathField();
@@ -206,6 +193,9 @@ class MathInput extends React.Component<Props, State> {
206
193
  if (this.props.value !== value) {
207
194
  this.props.onChange(value);
208
195
  }
196
+ this.setState({
197
+ cursorContext: getCursorContext(mathField),
198
+ });
209
199
  },
210
200
  enter: () => {
211
201
  // NOTE(kevinb): This isn't how answers to exercises are
@@ -239,9 +229,31 @@ class MathInput extends React.Component<Props, State> {
239
229
  this.setState({focused: true});
240
230
  };
241
231
 
242
- blur: () => void = () => {
243
- this.mathField()?.blur();
244
- this.setState({focused: false});
232
+ // removing mathfield focus here makes the cursor vanished when the
233
+ // input is still focused
234
+ blur: () => void = () => this.setState({focused: false});
235
+
236
+ handleKeypadPress: (key: Keys, e: any) => void = (key, e) => {
237
+ const translator = keyTranslator[key];
238
+ const mathField = this.mathField();
239
+
240
+ if (mathField) {
241
+ if (translator) {
242
+ translator(mathField, key);
243
+ }
244
+ this.setState({
245
+ cursorContext: getCursorContext(mathField),
246
+ });
247
+ }
248
+
249
+ // We want to prevent taking focus from input when clicking on keypad
250
+ // Clickable handles "onClick" differently than react;
251
+ // a keyboard event is "keydown" type.
252
+ // In react without WonderBlocks, "enter" or "space" keydown events
253
+ // are also "click" events, differentiated by "detail".
254
+ if (e.type === "click") {
255
+ this.focus();
256
+ }
245
257
  };
246
258
 
247
259
  render(): React.ReactNode {
@@ -258,36 +270,202 @@ class MathInput extends React.Component<Props, State> {
258
270
  className = className + " " + this.props.className;
259
271
  }
260
272
 
261
- let buttons = null;
262
- if (this._shouldShowButtons()) {
263
- // @ts-expect-error - TS2322 - Type 'Element' is not assignable to type 'null'.
264
- buttons = (
265
- <TexButtons
266
- sets={this.props.buttonSets}
267
- className="math-input-buttons absolute"
268
- convertDotToTimes={this.props.convertDotToTimes}
269
- onInsert={this.insert}
270
- />
271
- );
272
- }
273
-
274
273
  return (
275
- <div style={{display: "inline-block"}}>
276
- <div style={{display: "inline-block"}}>
274
+ <View
275
+ style={[
276
+ styles.outerWrapper,
277
+ this.state.focused && styles.wrapperFocused,
278
+ this.props.hasError && styles.wrapperError,
279
+ ]}
280
+ >
281
+ <div
282
+ style={{
283
+ display: "flex",
284
+ padding: 1,
285
+ }}
286
+ onClick={() => {
287
+ const mathField = this.mathField();
288
+ if (!mathField) {
289
+ return;
290
+ }
291
+ this.setState({
292
+ cursorContext: getCursorContext(mathField),
293
+ });
294
+ }}
295
+ >
277
296
  <span
278
297
  className={className}
279
298
  ref={(ref) => (this.__mathFieldWrapperRef = ref)}
280
299
  aria-label={this.props.labelText}
281
- onFocus={this.handleFocus}
282
- onBlur={this.handleBlur}
300
+ onFocus={() => this.focus()}
301
+ onBlur={() => this.blur()}
283
302
  />
303
+ <Popover
304
+ opened={this.state.keypadOpen}
305
+ onClose={() => this.closeKeypad()}
306
+ dismissEnabled
307
+ content={() => (
308
+ <PopoverContentCore
309
+ closeButtonVisible
310
+ style={styles.popoverContent}
311
+ >
312
+ <DesktopKeypad
313
+ onAnalyticsEvent={
314
+ this.props.analytics.onAnalyticsEvent
315
+ }
316
+ extraKeys={this.props.extraKeys}
317
+ onClickKey={this.handleKeypadPress}
318
+ cursorContext={this.state.cursorContext}
319
+ convertDotToTimes={
320
+ this.props.convertDotToTimes
321
+ }
322
+ {...(this.props.keypadButtonSets ??
323
+ mapButtonSets(this.props?.buttonSets))}
324
+ />
325
+ </PopoverContentCore>
326
+ )}
327
+ >
328
+ {this.props.buttonsVisible === "never" ? (
329
+ <MathInputIcon
330
+ hovered={false}
331
+ focused={false}
332
+ active={false}
333
+ />
334
+ ) : (
335
+ <Clickable
336
+ aria-label={
337
+ this.state.keypadOpen
338
+ ? i18n._("close math keypad")
339
+ : i18n._("open math keypad")
340
+ }
341
+ aria-checked={this.state.keypadOpen}
342
+ role="switch"
343
+ onClick={() =>
344
+ this.state.keypadOpen
345
+ ? this.closeKeypad()
346
+ : this.openKeypad()
347
+ }
348
+ >
349
+ {(props) => (
350
+ <MathInputIcon
351
+ active={this.state.keypadOpen}
352
+ {...props}
353
+ />
354
+ )}
355
+ </Clickable>
356
+ )}
357
+ </Popover>
284
358
  </div>
285
- <div style={{position: "relative"}} onBlur={this.handleBlur}>
286
- {buttons}
287
- </div>
288
- </div>
359
+ </View>
289
360
  );
290
361
  }
291
362
  }
292
363
 
364
+ const MathInputIcon = ({hovered, focused, active}) => {
365
+ let color: string | undefined;
366
+ switch (true) {
367
+ case focused || active:
368
+ color = Color.white;
369
+ break;
370
+ case hovered:
371
+ color = Color.blue;
372
+ break;
373
+ default:
374
+ color = Color.offBlack;
375
+ break;
376
+ }
377
+ const dynamicClass =
378
+ active || focused ? styles.iconActive : styles.iconInactive;
379
+ return (
380
+ <View style={[styles.iconContainer, dynamicClass]}>
381
+ <svg
382
+ xmlns="http://www.w3.org/2000/svg"
383
+ width="16"
384
+ height="16"
385
+ fill={color}
386
+ viewBox="0 0 256 256"
387
+ >
388
+ <path d="M112,72a8,8,0,0,1-8,8H40a8,8,0,0,1,0-16h64A8,8,0,0,1,112,72Zm-8,104H80V152a8,8,0,0,0-16,0v24H40a8,8,0,0,0,0,16H64v24a8,8,0,0,0,16,0V192h24a8,8,0,0,0,0-16Zm48,0h64a8,8,0,0,0,0-16H152a8,8,0,0,0,0,16Zm64,16H152a8,8,0,0,0,0,16h64a8,8,0,0,0,0-16Zm-61.66-90.34a8,8,0,0,0,11.32,0L184,83.31l18.34,18.35a8,8,0,0,0,11.32-11.32L195.31,72l18.35-18.34a8,8,0,0,0-11.32-11.32L184,60.69,165.66,42.34a8,8,0,0,0-11.32,11.32L172.69,72,154.34,90.34A8,8,0,0,0,154.34,101.66Z" />
389
+ </svg>
390
+ </View>
391
+ );
392
+ };
393
+
394
+ const mapButtonSets = (buttonSets?: LegacyButtonSets) => {
395
+ const keypadButtonSets: KeypadButtonSets = {};
396
+ if (!buttonSets) {
397
+ return keypadButtonSets;
398
+ }
399
+ buttonSets.forEach((buttonSet) => {
400
+ switch (buttonSet) {
401
+ case "advanced relations":
402
+ keypadButtonSets.advancedRelations = true;
403
+ break;
404
+ case "basic relations":
405
+ keypadButtonSets.basicRelations = true;
406
+ break;
407
+ case "basic+div":
408
+ keypadButtonSets.divisionKey = true;
409
+ break;
410
+ case "logarithms":
411
+ keypadButtonSets.logarithms = true;
412
+ break;
413
+ case "prealgebra":
414
+ keypadButtonSets.preAlgebra = true;
415
+ break;
416
+ case "trig":
417
+ keypadButtonSets.trigonometry = true;
418
+ break;
419
+ case "basic":
420
+ default:
421
+ break;
422
+ }
423
+ });
424
+ return keypadButtonSets;
425
+ };
426
+
427
+ const inputFocused = {
428
+ borderWidth: 2,
429
+ borderColor: Color.blue,
430
+ margin: -1,
431
+ };
432
+
433
+ const styles = StyleSheet.create({
434
+ iconContainer: {
435
+ display: "flex",
436
+ justifyContent: "center",
437
+ height: "100%",
438
+ padding: Spacing.xxxSmall_4,
439
+ borderRadius: 1,
440
+ },
441
+ iconInactive: {
442
+ backgroundColor: Color.offBlack8,
443
+ },
444
+ iconActive: {
445
+ backgroundColor: Color.offBlack64,
446
+ },
447
+ outerWrapper: {
448
+ display: "inline-block",
449
+ borderStyle: "solid",
450
+ borderWidth: 1,
451
+ borderColor: Color.offBlack50,
452
+ borderRadius: 3,
453
+ background: Color.white,
454
+ ":hover": inputFocused,
455
+ },
456
+ wrapperFocused: inputFocused,
457
+ wrapperError: {
458
+ borderColor: Color.red,
459
+ background: fade(Color.red, 0.08),
460
+ ":hover": {
461
+ borderColor: Color.red,
462
+ },
463
+ },
464
+ popoverContent: {
465
+ padding: 0,
466
+ paddingBottom: Spacing.xxSmall_6,
467
+ maxWidth: "initial",
468
+ },
469
+ });
470
+
293
471
  export default MathInput;
@@ -6,6 +6,7 @@ import _ from "underscore";
6
6
 
7
7
  import {getDependencies} from "../dependencies";
8
8
 
9
+ import type {LegacyButtonSets} from "../perseus-types";
9
10
  import type {Keys} from "@khanacademy/math-input";
10
11
 
11
12
  const prettyBig = {fontSize: "150%"} as const;
@@ -22,7 +23,7 @@ type ButtonSet = (props: any) => [
22
23
  ];
23
24
 
24
25
  type ButtonSets = {
25
- readonly [key: string]: ReadonlyArray<ButtonSet>;
26
+ readonly [key in LegacyButtonSets[number]]: ReadonlyArray<ButtonSet>;
26
27
  };
27
28
 
28
29
  // These are functions because we want to generate a new component for each use
@@ -285,7 +286,6 @@ class TexButtons extends React.Component<Props> {
285
286
  // for _.keys() to return the keys in an arbitrary order, but in
286
287
  // practice, they will be ordered as listed above.
287
288
  const sortedButtonSets = _.sortBy(this.props.sets, (setName) =>
288
- // @ts-expect-error - TS2345 - Argument of type 'string | number' is not assignable to parameter of type 'string'.
289
289
  _.keys(buttonSets).indexOf(setName),
290
290
  );
291
291
 
@@ -311,11 +311,21 @@ export type PerseusExplanationWidgetOptions = {
311
311
  static: boolean;
312
312
  };
313
313
 
314
+ export type LegacyButtonSets = ReadonlyArray<
315
+ | "basic"
316
+ | "basic+div"
317
+ | "trig"
318
+ | "prealgebra"
319
+ | "logarithms"
320
+ | "basic relations"
321
+ | "advanced relations"
322
+ >;
323
+
314
324
  export type PerseusExpressionWidgetOptions = {
315
325
  // The expression forms the answer may come in
316
326
  answerForms: ReadonlyArray<PerseusExpressionAnswerForm>;
317
327
  // Different buttons sets that can show in the expression. Options are "basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations"
318
- buttonSets: ReadonlyArray<string>;
328
+ buttonSets: LegacyButtonSets;
319
329
  // Variables that can be used as functions. Default: ["f", "g", "h"]
320
330
  functions: ReadonlyArray<string>;
321
331
  // Use x for rendering multiplication instead of a center dot.
@@ -598,8 +598,7 @@
598
598
  }
599
599
 
600
600
  .perseus-math-input.mq-editable-field.mq-math-mode {
601
- // Mimic native text input
602
- background: #fff;
601
+ background-color: transparent;
603
602
 
604
603
  // If the font-size is too small, super/subscripts become hard to input
605
604
  font-size: 18px;
@@ -607,11 +606,11 @@
607
606
  // Make the click target easier to hit
608
607
  min-width: 100px;
609
608
 
610
- // Mimic native text input w/o using -moz/-webkit-apperance: textfield
611
- // border-color: rgb(78, 187, 212);
612
- border-color: #a4a4a4;
609
+ border: unset;
613
610
 
614
- border-radius: 5px;
611
+ &.mq-focused {
612
+ box-shadow: unset;
613
+ }
615
614
 
616
615
  & > .mq-root-block {
617
616
  // A bit of extra whitespace here greatly improves legibility
@@ -26,12 +26,6 @@
26
26
  width: 210px;
27
27
  }
28
28
 
29
- &.show-error-tooltip
30
- .perseus-math-input.mq-editable-field.mq-math-mode
31
- > .mq-root-block {
32
- padding-right: 25px;
33
- }
34
-
35
29
  .perseus-formats-tooltip {
36
30
  width: 190px;
37
31
  }
@@ -10,7 +10,7 @@ import expressionExport from "../expression";
10
10
 
11
11
  import TestKeypadContextWrapper from "./test-keypad-context-wrapper";
12
12
 
13
- import type {PerseusItem} from "../../perseus-types";
13
+ import type {LegacyButtonSets, PerseusItem} from "../../perseus-types";
14
14
  import type {Keys as Key} from "@khanacademy/math-input";
15
15
 
16
16
  type StoryArgs = {
@@ -69,12 +69,12 @@ export const DesktopKitchenSink = (args: StoryArgs): React.ReactElement => {
69
69
  "logarithms",
70
70
  "basic relations",
71
71
  "advanced relations",
72
- ],
72
+ ] as LegacyButtonSets,
73
73
  };
74
74
 
75
75
  const keypadConfiguration = {
76
76
  keypadType: KeypadType.EXPRESSION,
77
- extraKeys: ["x", "y", "z"] as Key[],
77
+ extraKeys: ["x", "y", "z"] as Array<Key>,
78
78
  };
79
79
 
80
80
  return (
@@ -1,5 +1,5 @@
1
1
  import {it, describe, beforeEach} from "@jest/globals";
2
- import {screen, fireEvent} from "@testing-library/react";
2
+ import {screen} from "@testing-library/react";
3
3
  import userEvent from "@testing-library/user-event";
4
4
  import "@testing-library/jest-dom";
5
5
 
@@ -428,93 +428,20 @@ describe("error tooltip", () => {
428
428
  );
429
429
  });
430
430
 
431
- it("shows on error", () => {
431
+ it("shows error text in tooltip", async () => {
432
432
  // Arrange
433
433
  const {renderer} = renderQuestion(expressionItem2.question);
434
434
  const expression = renderer.findWidgets("expression 1")[0];
435
435
 
436
436
  // Act
437
437
  expression.insert("x&&&&&^1");
438
+ screen.getByRole("textbox").blur();
438
439
  renderer.guessAndScore();
439
- jest.runOnlyPendingTimers();
440
440
 
441
441
  // Assert
442
- const errorMessage = screen.getByText(
443
- "Sorry, I don't understand that!",
444
- );
445
- expect(errorMessage).not.toBeUndefined();
446
- });
447
-
448
- it("shows error text on mouse over", async () => {
449
- // Arrange
450
- const {renderer} = renderQuestion(expressionItem2.question);
451
- const expression = renderer.findWidgets("expression 1")[0];
452
-
453
- // Act
454
- expression.insert("x&&&&&^1");
455
- renderer.guessAndScore();
456
- jest.runOnlyPendingTimers();
457
-
458
- const tooltip = await screen.findByTestId("test-error-icon");
459
-
460
- // eslint-disable-next-line testing-library/prefer-user-event
461
- fireEvent.mouseEnter(tooltip);
462
-
463
- // Assert
464
- expect(
465
- screen.getByText("Sorry, I don't understand that!"),
466
- ).toBeVisible();
467
- });
468
- it("hides error text on mouse leave", async () => {
469
- // Arrange
470
- const {renderer} = renderQuestion(expressionItem2.question);
471
- const expression = renderer.findWidgets("expression 1")[0];
472
-
473
- expression.insert("x&&&&&^1");
474
- renderer.guessAndScore();
475
- jest.runOnlyPendingTimers();
476
-
477
- const tooltip = await screen.findByTestId("test-error-icon");
478
-
479
- // Act
480
- // NOTE(Nicole): The existing perseus code requires explicit events
481
- // eslint-disable-next-line testing-library/prefer-user-event
482
- fireEvent.mouseEnter(tooltip);
483
- // NOTE(Nicole): The existing perseus code requires explicit events
484
- // eslint-disable-next-line testing-library/prefer-user-event
485
- fireEvent.mouseLeave(tooltip);
486
-
487
- // Assert
488
- expect(
489
- screen.getByText("Sorry, I don't understand that!"),
490
- ).not.toBeVisible();
491
- });
492
-
493
- it("toggles error text on click", async () => {
494
- // Arrange
495
- const {renderer} = renderQuestion(expressionItem2.question);
496
- const expression = renderer.findWidgets("expression 1")[0];
497
-
498
- expression.insert("x&&&&&^1");
499
- renderer.guessAndScore();
500
- jest.runOnlyPendingTimers();
501
-
502
- const tooltip = await screen.findByTestId("test-error-icon");
503
-
504
- // Act & Assert
505
- // NOTE(Nicole): The existing perseus code requires explicit events
506
- // eslint-disable-next-line testing-library/prefer-user-event
507
- fireEvent.click(tooltip);
442
+ expect(screen.getByText("Oops!")).toBeVisible();
508
443
  expect(
509
444
  screen.getByText("Sorry, I don't understand that!"),
510
445
  ).toBeVisible();
511
-
512
- // Act & Assert
513
- // NOTE(Nicole): The existing perseus code requires explicit events
514
- // eslint-disable-next-line testing-library/prefer-user-event
515
- fireEvent.click(tooltip);
516
- expect(
517
- screen.getByText("Sorry, I don't understand that!"),
518
- ).not.toBeVisible();
519
446
  });
520
447
  });