@khanacademy/wonder-blocks-tooltip 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.
Files changed (36) hide show
  1. package/LICENSE +21 -0
  2. package/dist/es/index.js +1133 -0
  3. package/dist/index.js +1389 -0
  4. package/dist/index.js.flow +2 -0
  5. package/docs.md +11 -0
  6. package/package.json +37 -0
  7. package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +2674 -0
  8. package/src/__tests__/generated-snapshot.test.js +475 -0
  9. package/src/components/__tests__/__snapshots__/tooltip-tail.test.js.snap +9 -0
  10. package/src/components/__tests__/__snapshots__/tooltip.test.js.snap +47 -0
  11. package/src/components/__tests__/tooltip-anchor.test.js +987 -0
  12. package/src/components/__tests__/tooltip-bubble.test.js +80 -0
  13. package/src/components/__tests__/tooltip-popper.test.js +71 -0
  14. package/src/components/__tests__/tooltip-tail.test.js +117 -0
  15. package/src/components/__tests__/tooltip.integration.test.js +79 -0
  16. package/src/components/__tests__/tooltip.test.js +401 -0
  17. package/src/components/tooltip-anchor.js +330 -0
  18. package/src/components/tooltip-bubble.js +150 -0
  19. package/src/components/tooltip-bubble.md +92 -0
  20. package/src/components/tooltip-content.js +76 -0
  21. package/src/components/tooltip-content.md +34 -0
  22. package/src/components/tooltip-popper.js +101 -0
  23. package/src/components/tooltip-tail.js +462 -0
  24. package/src/components/tooltip-tail.md +143 -0
  25. package/src/components/tooltip.js +235 -0
  26. package/src/components/tooltip.md +194 -0
  27. package/src/components/tooltip.stories.js +76 -0
  28. package/src/index.js +12 -0
  29. package/src/util/__tests__/__snapshots__/active-tracker.test.js.snap +3 -0
  30. package/src/util/__tests__/__snapshots__/ref-tracker.test.js.snap +3 -0
  31. package/src/util/__tests__/active-tracker.test.js +142 -0
  32. package/src/util/__tests__/ref-tracker.test.js +153 -0
  33. package/src/util/active-tracker.js +94 -0
  34. package/src/util/constants.js +7 -0
  35. package/src/util/ref-tracker.js +46 -0
  36. package/src/util/types.js +29 -0
@@ -0,0 +1,401 @@
1
+ // @flow
2
+ import * as React from "react";
3
+ import * as ReactDOM from "react-dom";
4
+ import {mount} from "enzyme";
5
+
6
+ import {View} from "@khanacademy/wonder-blocks-core";
7
+ import {Body, HeadingSmall} from "@khanacademy/wonder-blocks-typography";
8
+
9
+ import Tooltip from "../tooltip.js";
10
+ import TooltipBubble from "../tooltip-bubble.js";
11
+ import TooltipContent from "../tooltip-content.js";
12
+
13
+ const mockIDENTIFIER = "mock-identifier";
14
+ jest.mock("../tooltip-bubble.js");
15
+ jest.mock("@khanacademy/wonder-blocks-core", () => {
16
+ const Core = jest.requireActual("@khanacademy/wonder-blocks-core");
17
+ // We want all of Core to be the regular thing except for UniqueIDProvider
18
+ return {
19
+ ...Core,
20
+ UniqueIDProvider: (props) =>
21
+ props.children({
22
+ get: () => mockIDENTIFIER,
23
+ }),
24
+ };
25
+ });
26
+
27
+ describe("Tooltip", () => {
28
+ beforeEach(() => {
29
+ jest.clearAllMocks();
30
+ jest.clearAllTimers();
31
+ jest.useFakeTimers();
32
+ });
33
+
34
+ describe("content is a string, wraps in TooltipContent", () => {
35
+ test("with title", async () => {
36
+ // Arrange
37
+ const ref = await new Promise((resolve) => {
38
+ const nodes = (
39
+ <View>
40
+ <Tooltip id="tooltip" title="Title" content="Content">
41
+ <View ref={resolve}>Anchor</View>
42
+ </Tooltip>
43
+ </View>
44
+ );
45
+ mount(nodes);
46
+ });
47
+ const node = (ReactDOM.findDOMNode(ref): any);
48
+ node && node.dispatchEvent(new FocusEvent("focusin"));
49
+ jest.runOnlyPendingTimers();
50
+
51
+ // Act
52
+ // Flow doesn't like jest mocks
53
+ // $FlowFixMe[prop-missing]
54
+ const result = TooltipBubble.mock.instances[0].props["children"];
55
+
56
+ // Assert
57
+ expect(result).toMatchSnapshot(
58
+ `Similar to <TooltipContent title="Title">Content</TooltipContent>`,
59
+ );
60
+ });
61
+
62
+ test("without title", async () => {
63
+ // Arrange
64
+ const ref = await new Promise((resolve) => {
65
+ const nodes = (
66
+ <View>
67
+ <Tooltip id="tooltip" content="Content">
68
+ <View ref={resolve}>Anchor</View>
69
+ </Tooltip>
70
+ </View>
71
+ );
72
+ mount(nodes);
73
+ });
74
+ const node = (ReactDOM.findDOMNode(ref): any);
75
+ node && node.dispatchEvent(new FocusEvent("focusin"));
76
+ jest.runOnlyPendingTimers();
77
+
78
+ // Act
79
+ // Flow doesn't like jest mocks
80
+ // $FlowFixMe[prop-missing]
81
+ const result = TooltipBubble.mock.instances[0].props["children"];
82
+
83
+ // Assert
84
+ expect(result).toMatchSnapshot(
85
+ `Similar to <TooltipContent>Content</TooltipContent>`,
86
+ );
87
+ });
88
+ });
89
+
90
+ describe("content is TooltipContent", () => {
91
+ test("with title, sets title of TooltipContent", async () => {
92
+ // Arrange
93
+ const ref = await new Promise((resolve) => {
94
+ const content = (
95
+ <TooltipContent>
96
+ <HeadingSmall>Some custom content</HeadingSmall>
97
+ </TooltipContent>
98
+ );
99
+ const title = <HeadingSmall>Title</HeadingSmall>;
100
+ const nodes = (
101
+ <View>
102
+ <Tooltip id="tooltip" title={title} content={content}>
103
+ <View ref={resolve}>Anchor</View>
104
+ </Tooltip>
105
+ </View>
106
+ );
107
+ mount(nodes);
108
+ });
109
+ const node = (ReactDOM.findDOMNode(ref): any);
110
+ node && node.dispatchEvent(new KeyboardEvent("focusin"));
111
+ jest.runOnlyPendingTimers();
112
+
113
+ // Act
114
+ // Flow doesn't like jest mocks
115
+ // $FlowFixMe[prop-missing]
116
+ const result = TooltipBubble.mock.instances[0].props["children"];
117
+
118
+ // Assert
119
+ expect(result.props["title"]).toMatchSnapshot(
120
+ `Similar to <HeadingSmall>Title</HeadingSmall>`,
121
+ );
122
+ expect(result.props["children"]).toMatchSnapshot(
123
+ `Similar to <HeadingSmall>Some custom content</HeadingSmall>`,
124
+ );
125
+ });
126
+
127
+ test("with title, overrides title of TooltipContent", async () => {
128
+ // Arrange
129
+ const ref = await new Promise((resolve) => {
130
+ const content = (
131
+ <TooltipContent title="Content title">
132
+ <Body>Some custom content</Body>
133
+ </TooltipContent>
134
+ );
135
+ const nodes = (
136
+ <View>
137
+ <Tooltip id="tooltip" title="Title" content={content}>
138
+ <View ref={resolve}>Anchor</View>
139
+ </Tooltip>
140
+ </View>
141
+ );
142
+ mount(nodes);
143
+ });
144
+ const node = (ReactDOM.findDOMNode(ref): any);
145
+ node && node.dispatchEvent(new KeyboardEvent("focusin"));
146
+ jest.runOnlyPendingTimers();
147
+
148
+ // Act
149
+ // Flow doesn't like jest mocks
150
+ // $FlowFixMe[prop-missing]
151
+ const result = TooltipBubble.mock.instances[0].props["children"];
152
+
153
+ // Assert
154
+ expect(result.props["title"]).toBe("Title");
155
+ expect(result.props["children"]).toMatchSnapshot(
156
+ `Similar to <Body>Some custom content</Body>`,
157
+ );
158
+ });
159
+
160
+ test("without title, renders content as-is", async () => {
161
+ // Arrange
162
+ const ref = await new Promise((resolve) => {
163
+ const content = (
164
+ <TooltipContent title="Content title">
165
+ <Body>Some custom content</Body>
166
+ </TooltipContent>
167
+ );
168
+ const nodes = (
169
+ <View>
170
+ <Tooltip id="tooltip" content={content}>
171
+ <View ref={resolve}>Anchor</View>
172
+ </Tooltip>
173
+ </View>
174
+ );
175
+ mount(nodes);
176
+ });
177
+ const node = (ReactDOM.findDOMNode(ref): any);
178
+ node && node.dispatchEvent(new KeyboardEvent("focusin"));
179
+ jest.runOnlyPendingTimers();
180
+
181
+ // Act
182
+ // Flow doesn't like jest mocks
183
+ // $FlowFixMe[prop-missing]
184
+ const result = TooltipBubble.mock.instances[0].props["children"];
185
+
186
+ // Assert
187
+ expect(result.props["title"]).toBe("Content title");
188
+ expect(result.props["children"]).toMatchSnapshot(
189
+ `<Body>Some custom content</Body>`,
190
+ );
191
+ });
192
+ });
193
+
194
+ describe("accessibility", () => {
195
+ test("no id, sets identifier of TooltipBubble with UniqueIDProvider", async () => {
196
+ // Arrange
197
+ const ref = await new Promise((resolve) => {
198
+ const nodes = (
199
+ <View>
200
+ <Tooltip content="Content">
201
+ <View ref={resolve}>Anchor</View>
202
+ </Tooltip>
203
+ </View>
204
+ );
205
+ mount(nodes);
206
+ });
207
+ const node = (ReactDOM.findDOMNode(ref): any);
208
+ node && node.dispatchEvent(new KeyboardEvent("focusin"));
209
+ jest.runOnlyPendingTimers();
210
+
211
+ // Act
212
+ // Flow doesn't like jest mocks
213
+ // $FlowFixMe[prop-missing]
214
+ const result = TooltipBubble.mock.instances[0].props["id"];
215
+
216
+ // Assert
217
+ expect(result).toBe(mockIDENTIFIER);
218
+ });
219
+
220
+ test("custom id, sets identifier of TooltipBubble", async () => {
221
+ // Arrange
222
+ const tooltipID = "tooltip-1";
223
+ const ref = await new Promise((resolve) => {
224
+ const nodes = (
225
+ <View>
226
+ <Tooltip id={tooltipID} title="Title" content="Content">
227
+ <View ref={resolve}>Anchor</View>
228
+ </Tooltip>
229
+ </View>
230
+ );
231
+ mount(nodes);
232
+ });
233
+ const node = (ReactDOM.findDOMNode(ref): any);
234
+ node && node.dispatchEvent(new KeyboardEvent("focusin"));
235
+ jest.runOnlyPendingTimers();
236
+
237
+ // Act
238
+ // Flow doesn't like jest mocks
239
+ // $FlowFixMe[prop-missing]
240
+ const result = TooltipBubble.mock.instances[0].props["id"];
241
+
242
+ // Assert
243
+ expect(result).toBe(tooltipID);
244
+ });
245
+
246
+ describe("text-only anchor", () => {
247
+ test("wraps with element", async () => {
248
+ // Arrange
249
+ const ref = await new Promise((resolve) => {
250
+ const nodes = (
251
+ <View>
252
+ <Tooltip ref={resolve} content="Content">
253
+ Anchor
254
+ </Tooltip>
255
+ </View>
256
+ );
257
+ mount(nodes);
258
+ });
259
+
260
+ // Act
261
+ const result = (ReactDOM.findDOMNode(ref): any);
262
+
263
+ // Assert
264
+ expect(result).toBeInstanceOf(HTMLSpanElement);
265
+ expect(result.innerHTML).toBe("Anchor");
266
+ });
267
+
268
+ test("id provided, does not attach aria-describedby", async () => {
269
+ // Arrange
270
+ const tooltipID = "tooltip-2";
271
+ const ref = await new Promise((resolve) => {
272
+ const nodes = (
273
+ <View>
274
+ <Tooltip
275
+ ref={resolve}
276
+ id={tooltipID}
277
+ content="Content"
278
+ >
279
+ Anchor
280
+ </Tooltip>
281
+ </View>
282
+ );
283
+ mount(nodes);
284
+ });
285
+ const node = (ReactDOM.findDOMNode(ref): any);
286
+
287
+ // Act
288
+ const result = node.getAttribute("aria-describedby");
289
+
290
+ // Assert
291
+ expect(result).toBeNull();
292
+ });
293
+
294
+ test("no id provided, attaches aria-describedby", async () => {
295
+ // Arrange
296
+ const ref = await new Promise((resolve) => {
297
+ const nodes = (
298
+ <View>
299
+ <Tooltip ref={resolve} content="Content">
300
+ Anchor
301
+ </Tooltip>
302
+ </View>
303
+ );
304
+ mount(nodes);
305
+ });
306
+ const node = (ReactDOM.findDOMNode(ref): any);
307
+
308
+ // Act
309
+ const result = node.getAttribute("aria-describedby");
310
+
311
+ // Assert
312
+ expect(result).toBe(mockIDENTIFIER);
313
+ });
314
+ });
315
+
316
+ describe("element anchor", () => {
317
+ test("does not wrap", async () => {
318
+ // Arrange
319
+ const anchor = (
320
+ <View>
321
+ <View>Anchor</View>
322
+ </View>
323
+ );
324
+ const ref = await new Promise((resolve) => {
325
+ const nodes = (
326
+ <View>
327
+ <Tooltip ref={resolve} content="Content">
328
+ {anchor}
329
+ </Tooltip>
330
+ </View>
331
+ );
332
+ mount(nodes);
333
+ });
334
+
335
+ // Act
336
+ const result = (ReactDOM.findDOMNode(ref): any);
337
+
338
+ // Assert
339
+ expect(result).toBeInstanceOf(HTMLDivElement);
340
+ expect(result.innerHTML).not.toBe("Anchor");
341
+ expect(result.children[0].innerHTML).toBe("Anchor");
342
+ });
343
+
344
+ test("id provided, does not attach aria-describedby", async () => {
345
+ // Arrange
346
+ const anchor = (
347
+ <View>
348
+ <View>Anchor</View>
349
+ </View>
350
+ );
351
+ const ref = await new Promise((resolve) => {
352
+ const nodes = (
353
+ <View>
354
+ <Tooltip
355
+ id="tooltip-3"
356
+ ref={resolve}
357
+ content="Content"
358
+ >
359
+ {anchor}
360
+ </Tooltip>
361
+ </View>
362
+ );
363
+ mount(nodes);
364
+ });
365
+ const node = (ReactDOM.findDOMNode(ref): any);
366
+
367
+ // Act
368
+ const result = node.getAttribute("aria-describedby");
369
+
370
+ // Assert
371
+ expect(result).toBeNull();
372
+ });
373
+
374
+ test("no id provided, attaches aria-describedby", async () => {
375
+ // Arrange
376
+ const anchor = (
377
+ <View>
378
+ <View>Anchor</View>
379
+ </View>
380
+ );
381
+ const ref = await new Promise((resolve) => {
382
+ const nodes = (
383
+ <View>
384
+ <Tooltip ref={resolve} content="Content">
385
+ {anchor}
386
+ </Tooltip>
387
+ </View>
388
+ );
389
+ mount(nodes);
390
+ });
391
+ const node = (ReactDOM.findDOMNode(ref): any);
392
+
393
+ // Act
394
+ const result = node.getAttribute("aria-describedby");
395
+
396
+ // Assert
397
+ expect(result).toBe(mockIDENTIFIER);
398
+ });
399
+ });
400
+ });
401
+ });