@khanacademy/wonder-blocks-tooltip 1.4.0 → 1.4.2

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,24 +1,24 @@
1
1
  // @flow
2
2
  import * as React from "react";
3
3
  import * as ReactDOM from "react-dom";
4
- import {mount} from "enzyme";
5
- import "jest-enzyme";
4
+ import {render, screen} from "@testing-library/react";
5
+ import userEvent from "@testing-library/user-event";
6
6
 
7
7
  import {View} from "@khanacademy/wonder-blocks-core";
8
- import {Body, HeadingSmall} from "@khanacademy/wonder-blocks-typography";
9
8
 
10
9
  import Tooltip from "../tooltip.js";
11
- import TooltipBubble from "../tooltip-bubble.js";
12
- import TooltipContent from "../tooltip-content.js";
13
10
 
14
11
  const mockIDENTIFIER = "mock-identifier";
15
- jest.mock("../tooltip-bubble.js");
16
12
  jest.mock("@khanacademy/wonder-blocks-core", () => {
17
13
  const Core = jest.requireActual("@khanacademy/wonder-blocks-core");
18
14
  // We want all of Core to be the regular thing except for UniqueIDProvider
19
15
  return {
20
16
  ...Core,
21
17
  UniqueIDProvider: (props) =>
18
+ // NOTE(kevinb): We aren't actually access the DOM here. The logic
19
+ // used by this lint rule to determine DOM access could be more
20
+ // precise.
21
+ // eslint-disable-next-line testing-library/no-node-access
22
22
  props.children({
23
23
  get: () => mockIDENTIFIER,
24
24
  }),
@@ -32,234 +32,144 @@ describe("Tooltip", () => {
32
32
  jest.useFakeTimers();
33
33
  });
34
34
 
35
- describe("content is a string, wraps in TooltipContent", () => {
36
- test("with title", async () => {
35
+ describe("basic operations", () => {
36
+ it("should not show the tooltip to being with", () => {
37
37
  // Arrange
38
- const ref = await new Promise((resolve) => {
39
- const nodes = (
40
- <View>
41
- <Tooltip id="tooltip" title="Title" content="Content">
42
- <View ref={resolve}>Anchor</View>
43
- </Tooltip>
44
- </View>
45
- );
46
- mount(nodes);
47
- });
48
- const node = (ReactDOM.findDOMNode(ref): any);
49
- node && node.dispatchEvent(new FocusEvent("focusin"));
50
- jest.runOnlyPendingTimers();
38
+ render(
39
+ <View>
40
+ <Tooltip title="Title" content="Content">
41
+ <View>Anchor</View>
42
+ </Tooltip>
43
+ </View>,
44
+ );
51
45
 
52
46
  // Act
53
- // Flow doesn't like jest mocks
54
- // $FlowFixMe[prop-missing]
55
- const result = TooltipBubble.mock.instances[0].props["children"];
47
+ const tooltip = screen.queryByRole("tooltip");
56
48
 
57
49
  // Assert
58
- expect(result).toMatchSnapshot(
59
- `Similar to <TooltipContent title="Title">Content</TooltipContent>`,
60
- );
50
+ expect(tooltip).not.toBeInTheDocument();
61
51
  });
62
52
 
63
- test("without title", async () => {
53
+ it("should show the tooltip on hover", async () => {
64
54
  // Arrange
65
- const ref = await new Promise((resolve) => {
66
- const nodes = (
67
- <View>
68
- <Tooltip id="tooltip" content="Content">
69
- <View ref={resolve}>Anchor</View>
70
- </Tooltip>
71
- </View>
72
- );
73
- mount(nodes);
74
- });
75
- const node = (ReactDOM.findDOMNode(ref): any);
76
- node && node.dispatchEvent(new FocusEvent("focusin"));
77
- jest.runOnlyPendingTimers();
78
-
79
- // Act
80
- // Flow doesn't like jest mocks
81
- // $FlowFixMe[prop-missing]
82
- const result = TooltipBubble.mock.instances[0].props["children"];
83
-
84
- // Assert
85
- expect(result).toMatchSnapshot(
86
- `Similar to <TooltipContent>Content</TooltipContent>`,
55
+ render(
56
+ <View>
57
+ <Tooltip title="Title" content="Content">
58
+ <View>Anchor</View>
59
+ </Tooltip>
60
+ </View>,
87
61
  );
88
- });
89
- });
90
62
 
91
- describe("content is TooltipContent", () => {
92
- test("with title, sets title of TooltipContent", async () => {
93
- // Arrange
94
- const ref = await new Promise((resolve) => {
95
- const content = (
96
- <TooltipContent>
97
- <HeadingSmall>Some custom content</HeadingSmall>
98
- </TooltipContent>
99
- );
100
- const title = <HeadingSmall>Title</HeadingSmall>;
101
- const nodes = (
102
- <View>
103
- <Tooltip id="tooltip" title={title} content={content}>
104
- <View ref={resolve}>Anchor</View>
105
- </Tooltip>
106
- </View>
107
- );
108
- mount(nodes);
109
- });
110
- const node = (ReactDOM.findDOMNode(ref): any);
111
- node && node.dispatchEvent(new KeyboardEvent("focusin"));
63
+ const node = screen.getByText("Anchor");
64
+ userEvent.hover(node);
112
65
  jest.runOnlyPendingTimers();
113
66
 
114
67
  // Act
115
- // Flow doesn't like jest mocks
116
- // $FlowFixMe[prop-missing]
117
- const result = TooltipBubble.mock.instances[0].props["children"];
68
+ const tooltip = screen.getByRole("tooltip");
118
69
 
119
70
  // Assert
120
- expect(result.props["title"]).toMatchSnapshot(
121
- `Similar to <HeadingSmall>Title</HeadingSmall>`,
122
- );
123
- expect(result.props["children"]).toMatchSnapshot(
124
- `Similar to <HeadingSmall>Some custom content</HeadingSmall>`,
125
- );
71
+ expect(tooltip).toBeInTheDocument();
126
72
  });
127
73
 
128
- test("with title, overrides title of TooltipContent", async () => {
74
+ it("should hide the tooltip on unhover", async () => {
129
75
  // Arrange
130
- const ref = await new Promise((resolve) => {
131
- const content = (
132
- <TooltipContent title="Content title">
133
- <Body>Some custom content</Body>
134
- </TooltipContent>
135
- );
136
- const nodes = (
137
- <View>
138
- <Tooltip id="tooltip" title="Title" content={content}>
139
- <View ref={resolve}>Anchor</View>
140
- </Tooltip>
141
- </View>
142
- );
143
- mount(nodes);
144
- });
145
- const node = (ReactDOM.findDOMNode(ref): any);
146
- node && node.dispatchEvent(new KeyboardEvent("focusin"));
76
+ render(
77
+ <View>
78
+ <Tooltip title="Title" content="Content">
79
+ <View>Anchor</View>
80
+ </Tooltip>
81
+ </View>,
82
+ );
83
+
84
+ const node = screen.getByText("Anchor");
85
+ userEvent.hover(node);
86
+ jest.runOnlyPendingTimers();
87
+ userEvent.unhover(node);
147
88
  jest.runOnlyPendingTimers();
148
89
 
149
90
  // Act
150
- // Flow doesn't like jest mocks
151
- // $FlowFixMe[prop-missing]
152
- const result = TooltipBubble.mock.instances[0].props["children"];
91
+ const tooltip = screen.queryByRole("tooltip");
153
92
 
154
93
  // Assert
155
- expect(result.props["title"]).toBe("Title");
156
- expect(result.props["children"]).toMatchSnapshot(
157
- `Similar to <Body>Some custom content</Body>`,
158
- );
94
+ expect(tooltip).not.toBeInTheDocument();
159
95
  });
160
96
 
161
- test("without title, renders content as-is", async () => {
97
+ it("should work when the anchor is text", async () => {
162
98
  // Arrange
163
- const ref = await new Promise((resolve) => {
164
- const content = (
165
- <TooltipContent title="Content title">
166
- <Body>Some custom content</Body>
167
- </TooltipContent>
168
- );
169
- const nodes = (
170
- <View>
171
- <Tooltip id="tooltip" content={content}>
172
- <View ref={resolve}>Anchor</View>
173
- </Tooltip>
174
- </View>
175
- );
176
- mount(nodes);
177
- });
178
- const node = (ReactDOM.findDOMNode(ref): any);
179
- node && node.dispatchEvent(new KeyboardEvent("focusin"));
99
+ render(
100
+ <View>
101
+ <Tooltip title="Title" content="Content">
102
+ Anchor
103
+ </Tooltip>
104
+ </View>,
105
+ );
106
+
107
+ const node = screen.getByText("Anchor");
108
+ userEvent.hover(node);
180
109
  jest.runOnlyPendingTimers();
181
110
 
182
111
  // Act
183
- // Flow doesn't like jest mocks
184
- // $FlowFixMe[prop-missing]
185
- const result = TooltipBubble.mock.instances[0].props["children"];
112
+ const tooltip = screen.getByRole("tooltip");
186
113
 
187
114
  // Assert
188
- expect(result.props["title"]).toBe("Content title");
189
- expect(result.props["children"]).toMatchSnapshot(
190
- `<Body>Some custom content</Body>`,
191
- );
115
+ expect(tooltip).toBeInTheDocument();
192
116
  });
193
117
  });
194
118
 
195
119
  describe("accessibility", () => {
196
120
  test("no id, sets identifier of TooltipBubble with UniqueIDProvider", async () => {
197
121
  // Arrange
198
- const ref = await new Promise((resolve) => {
199
- const nodes = (
200
- <View>
201
- <Tooltip content="Content">
202
- <View ref={resolve}>Anchor</View>
203
- </Tooltip>
204
- </View>
205
- );
206
- mount(nodes);
207
- });
208
- const node = (ReactDOM.findDOMNode(ref): any);
209
- node && node.dispatchEvent(new KeyboardEvent("focusin"));
122
+ render(
123
+ <View>
124
+ <Tooltip title="Title" content="Content">
125
+ <View>Anchor</View>
126
+ </Tooltip>
127
+ </View>,
128
+ );
129
+ const node = screen.getByText("Anchor");
130
+ userEvent.hover(node);
210
131
  jest.runOnlyPendingTimers();
211
132
 
212
133
  // Act
213
- // Flow doesn't like jest mocks
214
- // $FlowFixMe[prop-missing]
215
- const result = TooltipBubble.mock.instances[0].props["id"];
134
+ // eslint-disable-next-line testing-library/no-node-access
135
+ const result = document.querySelector("#" + mockIDENTIFIER);
216
136
 
217
137
  // Assert
218
- expect(result).toBe(mockIDENTIFIER);
138
+ expect(result).toBeInTheDocument();
219
139
  });
220
140
 
221
141
  test("custom id, sets identifier of TooltipBubble", async () => {
222
142
  // Arrange
223
- const tooltipID = "tooltip-1";
224
- const ref = await new Promise((resolve) => {
225
- const nodes = (
226
- <View>
227
- <Tooltip id={tooltipID} title="Title" content="Content">
228
- <View ref={resolve}>Anchor</View>
229
- </Tooltip>
230
- </View>
231
- );
232
- mount(nodes);
233
- });
234
- const node = (ReactDOM.findDOMNode(ref): any);
235
- node && node.dispatchEvent(new KeyboardEvent("focusin"));
143
+ render(
144
+ <View>
145
+ <Tooltip id="tooltip-1" title="Title" content="Content">
146
+ <View>Anchor</View>
147
+ </Tooltip>
148
+ </View>,
149
+ );
150
+ const node = screen.getByText("Anchor");
151
+ userEvent.hover(node);
236
152
  jest.runOnlyPendingTimers();
237
153
 
238
154
  // Act
239
- // Flow doesn't like jest mocks
240
- // $FlowFixMe[prop-missing]
241
- const result = TooltipBubble.mock.instances[0].props["id"];
155
+ // eslint-disable-next-line testing-library/no-node-access
156
+ const result = document.querySelector("#tooltip-1");
242
157
 
243
158
  // Assert
244
- expect(result).toBe(tooltipID);
159
+ expect(result).toBeInTheDocument();
245
160
  });
246
161
 
247
162
  describe("text-only anchor", () => {
248
163
  test("wraps with element", async () => {
249
164
  // Arrange
250
- const ref = await new Promise((resolve) => {
251
- const nodes = (
252
- <View>
253
- <Tooltip ref={resolve} content="Content">
254
- Anchor
255
- </Tooltip>
256
- </View>
257
- );
258
- mount(nodes);
259
- });
165
+ render(
166
+ <View>
167
+ <Tooltip content="Content">Anchor</Tooltip>
168
+ </View>,
169
+ );
260
170
 
261
171
  // Act
262
- const result = (ReactDOM.findDOMNode(ref): any);
172
+ const result = screen.getByText("Anchor");
263
173
 
264
174
  // Assert
265
175
  expect(result).toBeInstanceOf(HTMLSpanElement);
@@ -268,22 +178,14 @@ describe("Tooltip", () => {
268
178
 
269
179
  test("id provided, does not attach aria-describedby", async () => {
270
180
  // Arrange
271
- const tooltipID = "tooltip-2";
272
- const ref = await new Promise((resolve) => {
273
- const nodes = (
274
- <View>
275
- <Tooltip
276
- ref={resolve}
277
- id={tooltipID}
278
- content="Content"
279
- >
280
- Anchor
281
- </Tooltip>
282
- </View>
283
- );
284
- mount(nodes);
285
- });
286
- const node = (ReactDOM.findDOMNode(ref): any);
181
+ render(
182
+ <View>
183
+ <Tooltip id="tooltip-2" content="Content">
184
+ Anchor
185
+ </Tooltip>
186
+ </View>,
187
+ );
188
+ const node = screen.getByText("Anchor");
287
189
 
288
190
  // Act
289
191
  const result = node.getAttribute("aria-describedby");
@@ -294,23 +196,20 @@ describe("Tooltip", () => {
294
196
 
295
197
  test("no id provided, attaches aria-describedby", async () => {
296
198
  // Arrange
297
- const ref = await new Promise((resolve) => {
298
- const nodes = (
299
- <View>
300
- <Tooltip ref={resolve} content="Content">
301
- Anchor
302
- </Tooltip>
303
- </View>
304
- );
305
- mount(nodes);
306
- });
307
- const node = (ReactDOM.findDOMNode(ref): any);
199
+ render(
200
+ <View>
201
+ <Tooltip content="Content">Anchor</Tooltip>
202
+ </View>,
203
+ );
204
+ const node = screen.getByText("Anchor");
308
205
 
309
206
  // Act
310
- const result = node.getAttribute("aria-describedby");
311
207
 
312
208
  // Assert
313
- expect(result).toBe(mockIDENTIFIER);
209
+ expect(node).toHaveAttribute(
210
+ "aria-describedby",
211
+ mockIDENTIFIER,
212
+ );
314
213
  });
315
214
  });
316
215
 
@@ -323,14 +222,13 @@ describe("Tooltip", () => {
323
222
  </View>
324
223
  );
325
224
  const ref = await new Promise((resolve) => {
326
- const nodes = (
225
+ render(
327
226
  <View>
328
227
  <Tooltip ref={resolve} content="Content">
329
228
  {anchor}
330
229
  </Tooltip>
331
- </View>
230
+ </View>,
332
231
  );
333
- mount(nodes);
334
232
  });
335
233
 
336
234
  // Act
@@ -339,6 +237,7 @@ describe("Tooltip", () => {
339
237
  // Assert
340
238
  expect(result).toBeInstanceOf(HTMLDivElement);
341
239
  expect(result.innerHTML).not.toBe("Anchor");
240
+ // eslint-disable-next-line testing-library/no-node-access
342
241
  expect(result.children[0].innerHTML).toBe("Anchor");
343
242
  });
344
243
 
@@ -350,7 +249,7 @@ describe("Tooltip", () => {
350
249
  </View>
351
250
  );
352
251
  const ref = await new Promise((resolve) => {
353
- const nodes = (
252
+ render(
354
253
  <View>
355
254
  <Tooltip
356
255
  id="tooltip-3"
@@ -359,9 +258,8 @@ describe("Tooltip", () => {
359
258
  >
360
259
  {anchor}
361
260
  </Tooltip>
362
- </View>
261
+ </View>,
363
262
  );
364
- mount(nodes);
365
263
  });
366
264
  const node = (ReactDOM.findDOMNode(ref): any);
367
265
 
@@ -380,14 +278,13 @@ describe("Tooltip", () => {
380
278
  </View>
381
279
  );
382
280
  const ref = await new Promise((resolve) => {
383
- const nodes = (
281
+ render(
384
282
  <View>
385
283
  <Tooltip ref={resolve} content="Content">
386
284
  {anchor}
387
285
  </Tooltip>
388
- </View>
286
+ </View>,
389
287
  );
390
- mount(nodes);
391
288
  });
392
289
  const node = (ReactDOM.findDOMNode(ref): any);
393
290
 
@@ -403,50 +300,36 @@ describe("Tooltip", () => {
403
300
  describe("Controlled", () => {
404
301
  test("can be opened programmatically", async () => {
405
302
  // Arrange
406
- await new Promise((resolve) => {
407
- const nodes = (
408
- <View>
409
- <Tooltip id="tooltip" content="Content" opened={true}>
410
- <View ref={resolve}>Anchor</View>
411
- </Tooltip>
412
- </View>
413
- );
414
- mount(nodes);
415
- });
416
- jest.runOnlyPendingTimers();
303
+ render(
304
+ <View>
305
+ <Tooltip id="tooltip" content="Content" opened={true}>
306
+ <View>Anchor</View>
307
+ </Tooltip>
308
+ </View>,
309
+ );
417
310
 
418
311
  // Act
419
- // Flow doesn't like jest mocks
420
- // $FlowFixMe[prop-missing]
421
- const result = TooltipBubble.mock.instances[0].props["children"];
312
+ jest.runOnlyPendingTimers();
422
313
 
423
314
  // Assert
424
- expect(result).toMatchSnapshot(
425
- `Similar to <TooltipContent>Content</TooltipContent>`,
426
- );
315
+ expect(screen.getByText("Content")).toBeInTheDocument();
427
316
  });
428
317
 
429
318
  test("can be closed programmatically", async () => {
430
319
  // Arrange
431
- await new Promise((resolve) => {
432
- const nodes = (
433
- <View>
434
- <Tooltip id="tooltip" content="Content" opened={false}>
435
- <View ref={resolve}>Anchor</View>
436
- </Tooltip>
437
- </View>
438
- );
439
- mount(nodes);
440
- });
441
- jest.runOnlyPendingTimers();
320
+ render(
321
+ <View>
322
+ <Tooltip id="tooltip" content="Content" opened={false}>
323
+ <View>Anchor</View>
324
+ </Tooltip>
325
+ </View>,
326
+ );
442
327
 
443
328
  // Act
444
- // Flow doesn't like jest mocks
445
- // $FlowFixMe[prop-missing]
446
- const result = TooltipBubble.mock.instances[0];
329
+ jest.runOnlyPendingTimers();
447
330
 
448
331
  // Assert
449
- expect(result).toBeUndefined();
332
+ expect(screen.queryByText("Content")).not.toBeInTheDocument();
450
333
  });
451
334
  });
452
335
  });
@@ -247,24 +247,6 @@ export default class TooltipTail extends React.Component<Props> {
247
247
  ];
248
248
  }
249
249
 
250
- _minDistanceFromCorners(placement: Placement): number {
251
- const minDistanceFromCornersForTopBottom = Spacing.medium_16;
252
- const minDistanceFromCornersForLeftRight = 7;
253
-
254
- switch (placement) {
255
- case "top":
256
- case "bottom":
257
- return minDistanceFromCornersForTopBottom;
258
-
259
- case "left":
260
- case "right":
261
- return minDistanceFromCornersForLeftRight;
262
-
263
- default:
264
- throw new Error(`Unknown placement: ${placement}`);
265
- }
266
- }
267
-
268
250
  _getFullTailWidth(): number {
269
251
  return ARROW_WIDTH + 2 * MIN_DISTANCE_FROM_CORNERS;
270
252
  }
@@ -1,9 +1,7 @@
1
1
  // @flow
2
2
  import * as React from "react";
3
3
  import * as ReactDOM from "react-dom";
4
- import {mount} from "enzyme";
5
- import "jest-enzyme";
6
-
4
+ import {render} from "@testing-library/react";
7
5
  import {View} from "@khanacademy/wonder-blocks-core";
8
6
 
9
7
  import RefTracker from "../ref-tracker.js";
@@ -57,7 +55,7 @@ describe("RefTracker", () => {
57
55
  <View ref={resolve} />
58
56
  </View>
59
57
  );
60
- mount(nodes);
58
+ render(nodes);
61
59
  });
62
60
  const domNode = ReactDOM.findDOMNode(ref);
63
61
  tracker.updateRef(ref);
@@ -93,7 +91,7 @@ describe("RefTracker", () => {
93
91
  <View ref={resolve} />
94
92
  </View>
95
93
  );
96
- mount(nodes);
94
+ render(nodes);
97
95
  });
98
96
 
99
97
  // Act
@@ -115,7 +113,7 @@ describe("RefTracker", () => {
115
113
  <View ref={resolve} />
116
114
  </View>
117
115
  );
118
- mount(nodes);
116
+ render(nodes);
119
117
  });
120
118
  const domNode = ReactDOM.findDOMNode(ref);
121
119
 
@@ -138,7 +136,7 @@ describe("RefTracker", () => {
138
136
  <View ref={resolve} />
139
137
  </View>
140
138
  );
141
- mount(nodes);
139
+ render(nodes);
142
140
  });
143
141
  tracker.updateRef(ref);
144
142
  targetFn.mockClear();