@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.
- package/LICENSE +21 -0
- package/dist/es/index.js +1133 -0
- package/dist/index.js +1389 -0
- package/dist/index.js.flow +2 -0
- package/docs.md +11 -0
- package/package.json +37 -0
- package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +2674 -0
- package/src/__tests__/generated-snapshot.test.js +475 -0
- package/src/components/__tests__/__snapshots__/tooltip-tail.test.js.snap +9 -0
- package/src/components/__tests__/__snapshots__/tooltip.test.js.snap +47 -0
- package/src/components/__tests__/tooltip-anchor.test.js +987 -0
- package/src/components/__tests__/tooltip-bubble.test.js +80 -0
- package/src/components/__tests__/tooltip-popper.test.js +71 -0
- package/src/components/__tests__/tooltip-tail.test.js +117 -0
- package/src/components/__tests__/tooltip.integration.test.js +79 -0
- package/src/components/__tests__/tooltip.test.js +401 -0
- package/src/components/tooltip-anchor.js +330 -0
- package/src/components/tooltip-bubble.js +150 -0
- package/src/components/tooltip-bubble.md +92 -0
- package/src/components/tooltip-content.js +76 -0
- package/src/components/tooltip-content.md +34 -0
- package/src/components/tooltip-popper.js +101 -0
- package/src/components/tooltip-tail.js +462 -0
- package/src/components/tooltip-tail.md +143 -0
- package/src/components/tooltip.js +235 -0
- package/src/components/tooltip.md +194 -0
- package/src/components/tooltip.stories.js +76 -0
- package/src/index.js +12 -0
- package/src/util/__tests__/__snapshots__/active-tracker.test.js.snap +3 -0
- package/src/util/__tests__/__snapshots__/ref-tracker.test.js.snap +3 -0
- package/src/util/__tests__/active-tracker.test.js +142 -0
- package/src/util/__tests__/ref-tracker.test.js +153 -0
- package/src/util/active-tracker.js +94 -0
- package/src/util/constants.js +7 -0
- package/src/util/ref-tracker.js +46 -0
- package/src/util/types.js +29 -0
|
@@ -0,0 +1,987 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
2
|
+
/* eslint-disable no-unused-vars */
|
|
3
|
+
// @flow
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
6
|
+
import {mount} from "enzyme";
|
|
7
|
+
|
|
8
|
+
import TooltipAnchor from "../tooltip-anchor.js";
|
|
9
|
+
import {
|
|
10
|
+
TooltipAppearanceDelay,
|
|
11
|
+
TooltipDisappearanceDelay,
|
|
12
|
+
} from "../../util/constants.js";
|
|
13
|
+
|
|
14
|
+
jest.mock("../../util/active-tracker.js");
|
|
15
|
+
|
|
16
|
+
describe("TooltipAnchor", () => {
|
|
17
|
+
beforeEach(async () => {
|
|
18
|
+
// $FlowIgnore[method-unbinding]
|
|
19
|
+
if (typeof document.addEventListener.mockReset === "function") {
|
|
20
|
+
// $FlowIgnore[method-unbinding]
|
|
21
|
+
document.addEventListener.mockRestore();
|
|
22
|
+
}
|
|
23
|
+
// $FlowIgnore[method-unbinding]
|
|
24
|
+
if (typeof document.removeEventListener.mockReset === "function") {
|
|
25
|
+
// $FlowIgnore[method-unbinding]
|
|
26
|
+
document.removeEventListener.mockRestore();
|
|
27
|
+
}
|
|
28
|
+
jest.clearAllTimers();
|
|
29
|
+
jest.useFakeTimers();
|
|
30
|
+
|
|
31
|
+
const {default: ActiveTracker} = await import(
|
|
32
|
+
"../../util/active-tracker.js"
|
|
33
|
+
);
|
|
34
|
+
// We know there's one global instance of this import, so let's
|
|
35
|
+
// reset it.
|
|
36
|
+
// Flow doesn't know this is a mock
|
|
37
|
+
// $FlowFixMe[prop-missing]
|
|
38
|
+
const mockTracker = ActiveTracker.mock.instances[0];
|
|
39
|
+
mockTracker.steal.mockClear();
|
|
40
|
+
mockTracker.giveup.mockClear();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("on mount, subscribes to focus and hover events", () => {
|
|
44
|
+
// Arrange
|
|
45
|
+
const nodes = (
|
|
46
|
+
<TooltipAnchor anchorRef={() => {}} onActiveChanged={() => {}}>
|
|
47
|
+
Anchor text
|
|
48
|
+
</TooltipAnchor>
|
|
49
|
+
);
|
|
50
|
+
const addEventListenerSpy = jest.spyOn(
|
|
51
|
+
HTMLElement.prototype,
|
|
52
|
+
"addEventListener",
|
|
53
|
+
);
|
|
54
|
+
addEventListenerSpy.mockClear();
|
|
55
|
+
|
|
56
|
+
// Act
|
|
57
|
+
mount(nodes);
|
|
58
|
+
|
|
59
|
+
// Assert
|
|
60
|
+
expect(addEventListenerSpy).toHaveBeenCalledWith(
|
|
61
|
+
"focusin",
|
|
62
|
+
expect.any(Function),
|
|
63
|
+
);
|
|
64
|
+
expect(addEventListenerSpy).toHaveBeenCalledWith(
|
|
65
|
+
"focusout",
|
|
66
|
+
expect.any(Function),
|
|
67
|
+
);
|
|
68
|
+
expect(addEventListenerSpy).toHaveBeenCalledWith(
|
|
69
|
+
"mouseenter",
|
|
70
|
+
expect.any(Function),
|
|
71
|
+
);
|
|
72
|
+
expect(addEventListenerSpy).toHaveBeenCalledWith(
|
|
73
|
+
"mouseleave",
|
|
74
|
+
expect.any(Function),
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("on unmount, unsubscribes from focus and hover events", () => {
|
|
79
|
+
// Arrange
|
|
80
|
+
const nodes = (
|
|
81
|
+
<TooltipAnchor anchorRef={() => {}} onActiveChanged={() => {}}>
|
|
82
|
+
Anchor text
|
|
83
|
+
</TooltipAnchor>
|
|
84
|
+
);
|
|
85
|
+
const removeEventListenerSpy = jest.spyOn(
|
|
86
|
+
HTMLElement.prototype,
|
|
87
|
+
"removeEventListener",
|
|
88
|
+
);
|
|
89
|
+
removeEventListenerSpy.mockClear();
|
|
90
|
+
const wrapper = mount(nodes);
|
|
91
|
+
|
|
92
|
+
// Act
|
|
93
|
+
wrapper.unmount();
|
|
94
|
+
|
|
95
|
+
// Assert
|
|
96
|
+
expect(removeEventListenerSpy).toHaveBeenCalledWith(
|
|
97
|
+
"focusin",
|
|
98
|
+
expect.any(Function),
|
|
99
|
+
);
|
|
100
|
+
expect(removeEventListenerSpy).toHaveBeenCalledWith(
|
|
101
|
+
"focusout",
|
|
102
|
+
expect.any(Function),
|
|
103
|
+
);
|
|
104
|
+
expect(removeEventListenerSpy).toHaveBeenCalledWith(
|
|
105
|
+
"mouseenter",
|
|
106
|
+
expect.any(Function),
|
|
107
|
+
);
|
|
108
|
+
expect(removeEventListenerSpy).toHaveBeenCalledWith(
|
|
109
|
+
"mouseleave",
|
|
110
|
+
expect.any(Function),
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe("forceAnchorFocusivity is true", () => {
|
|
115
|
+
test("if not set, sets tabindex on anchor target", async () => {
|
|
116
|
+
// Arrange
|
|
117
|
+
const ref = await new Promise((resolve) => {
|
|
118
|
+
const nodes = (
|
|
119
|
+
<TooltipAnchor
|
|
120
|
+
forceAnchorFocusivity={true}
|
|
121
|
+
anchorRef={resolve}
|
|
122
|
+
onActiveChanged={() => {}}
|
|
123
|
+
>
|
|
124
|
+
<View id="portal">This is the anchor</View>
|
|
125
|
+
</TooltipAnchor>
|
|
126
|
+
);
|
|
127
|
+
mount(nodes);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Act
|
|
131
|
+
const result = ref && ref.getAttribute("tabindex");
|
|
132
|
+
|
|
133
|
+
// Assert
|
|
134
|
+
expect(result).toBe("0");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("if tabindex already set, leaves it as-is", async () => {
|
|
138
|
+
// Arrange
|
|
139
|
+
const ref = await new Promise((resolve) => {
|
|
140
|
+
const nodes = (
|
|
141
|
+
<TooltipAnchor
|
|
142
|
+
forceAnchorFocusivity={true}
|
|
143
|
+
anchorRef={resolve}
|
|
144
|
+
onActiveChanged={() => {}}
|
|
145
|
+
>
|
|
146
|
+
<View tabIndex={-1}>This is the anchor</View>
|
|
147
|
+
</TooltipAnchor>
|
|
148
|
+
);
|
|
149
|
+
mount(nodes);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Act
|
|
153
|
+
const result = ref && ref.getAttribute("tabindex");
|
|
154
|
+
|
|
155
|
+
// Assert
|
|
156
|
+
expect(result).toBe("-1");
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe("forceAnchorFocusivity is false", () => {
|
|
161
|
+
test("does not set tabindex on anchor target", async () => {
|
|
162
|
+
// Arrange
|
|
163
|
+
const ref = await new Promise((resolve) => {
|
|
164
|
+
const nodes = (
|
|
165
|
+
<TooltipAnchor
|
|
166
|
+
forceAnchorFocusivity={false}
|
|
167
|
+
anchorRef={resolve}
|
|
168
|
+
onActiveChanged={() => {}}
|
|
169
|
+
>
|
|
170
|
+
<View>This is the anchor</View>
|
|
171
|
+
</TooltipAnchor>
|
|
172
|
+
);
|
|
173
|
+
mount(nodes);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Act
|
|
177
|
+
const result = ref && ref.getAttribute("tabindex");
|
|
178
|
+
|
|
179
|
+
// Assert
|
|
180
|
+
expect(result).toBeNull();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("if we had added tabindex, removes it", async () => {
|
|
184
|
+
// Arrange
|
|
185
|
+
let wrapper;
|
|
186
|
+
const ref = await new Promise((resolve) => {
|
|
187
|
+
const TestFixture = (props: any) => (
|
|
188
|
+
<TooltipAnchor
|
|
189
|
+
forceAnchorFocusivity={props.force}
|
|
190
|
+
anchorRef={resolve}
|
|
191
|
+
onActiveChanged={() => {}}
|
|
192
|
+
>
|
|
193
|
+
<View>This is the anchor</View>
|
|
194
|
+
</TooltipAnchor>
|
|
195
|
+
);
|
|
196
|
+
wrapper = mount(<TestFixture force={true} />);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Act
|
|
200
|
+
const tabindex = ref && ref.getAttribute("tabindex");
|
|
201
|
+
expect(tabindex).toBe("0");
|
|
202
|
+
|
|
203
|
+
wrapper && wrapper.setProps({force: false});
|
|
204
|
+
const result = ref && ref.getAttribute("tabindex");
|
|
205
|
+
|
|
206
|
+
// Assert
|
|
207
|
+
expect(result).toBeNull();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("if we had not added tabindex, leaves it", async () => {
|
|
211
|
+
// Arrange
|
|
212
|
+
const ref = await new Promise((resolve) => {
|
|
213
|
+
const TestFixture = (props: any) => (
|
|
214
|
+
<TooltipAnchor
|
|
215
|
+
forceAnchorFocusivity={props.force}
|
|
216
|
+
anchorRef={resolve}
|
|
217
|
+
onActiveChanged={() => {}}
|
|
218
|
+
>
|
|
219
|
+
<View tabIndex={-1}>This is the anchor</View>
|
|
220
|
+
</TooltipAnchor>
|
|
221
|
+
);
|
|
222
|
+
const wrapper = mount(<TestFixture force={true} />);
|
|
223
|
+
wrapper.setProps({force: false});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Act
|
|
227
|
+
const result = ref && ref.getAttribute("tabindex");
|
|
228
|
+
|
|
229
|
+
// Assert
|
|
230
|
+
expect(result).not.toBeNull();
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe("receives keyboard focus", () => {
|
|
235
|
+
test("active state was not stolen, delays set active", async () => {
|
|
236
|
+
// Arrange
|
|
237
|
+
const {default: ActiveTracker} = await import(
|
|
238
|
+
"../../util/active-tracker.js"
|
|
239
|
+
);
|
|
240
|
+
// Let's tell the tooltip it isn't stealing and therefore it should
|
|
241
|
+
// be using a delay to show the tooltip.
|
|
242
|
+
// Flow doesn't know this is a mock
|
|
243
|
+
// $FlowFixMe[prop-missing]
|
|
244
|
+
const mockTracker = ActiveTracker.mock.instances[0];
|
|
245
|
+
mockTracker.steal.mockImplementationOnce(() => false);
|
|
246
|
+
|
|
247
|
+
let activeState = false;
|
|
248
|
+
|
|
249
|
+
const ref = await new Promise((resolve) => {
|
|
250
|
+
const nodes = (
|
|
251
|
+
<TooltipAnchor
|
|
252
|
+
anchorRef={resolve}
|
|
253
|
+
onActiveChanged={(active) => {
|
|
254
|
+
activeState = active;
|
|
255
|
+
}}
|
|
256
|
+
>
|
|
257
|
+
Anchor Text
|
|
258
|
+
</TooltipAnchor>
|
|
259
|
+
);
|
|
260
|
+
mount(nodes);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// Act
|
|
264
|
+
// Let's fake a focusin (this is the event that the anchor gets
|
|
265
|
+
// whether focused directly or a child is focused). We have to
|
|
266
|
+
// fake directly because there's no real browser here handling
|
|
267
|
+
// focus and real events.
|
|
268
|
+
ref && ref.dispatchEvent(new FocusEvent("focusin"));
|
|
269
|
+
// Check that we didn't go active before the delay
|
|
270
|
+
expect(activeState).toBe(false);
|
|
271
|
+
expect(setTimeout).toHaveBeenLastCalledWith(
|
|
272
|
+
expect.any(Function),
|
|
273
|
+
TooltipAppearanceDelay,
|
|
274
|
+
);
|
|
275
|
+
jest.runOnlyPendingTimers();
|
|
276
|
+
|
|
277
|
+
// Assert
|
|
278
|
+
expect(activeState).toBe(true);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test("active state was stolen, set active immediately", async () => {
|
|
282
|
+
// Arrange
|
|
283
|
+
const {default: ActiveTracker} = await import(
|
|
284
|
+
"../../util/active-tracker.js"
|
|
285
|
+
);
|
|
286
|
+
// Let's tell the tooltip it is stealing and therefore it should
|
|
287
|
+
// not be using a delay to show the tooltip.
|
|
288
|
+
// Flow doesn't know this is a mock
|
|
289
|
+
// $FlowFixMe[prop-missing]
|
|
290
|
+
const mockTracker = ActiveTracker.mock.instances[0];
|
|
291
|
+
mockTracker.steal.mockImplementationOnce(() => true);
|
|
292
|
+
|
|
293
|
+
let activeState = false;
|
|
294
|
+
const ref = await new Promise((resolve) => {
|
|
295
|
+
const nodes = (
|
|
296
|
+
<TooltipAnchor
|
|
297
|
+
anchorRef={resolve}
|
|
298
|
+
onActiveChanged={(active) => {
|
|
299
|
+
activeState = active;
|
|
300
|
+
}}
|
|
301
|
+
>
|
|
302
|
+
Anchor Text
|
|
303
|
+
</TooltipAnchor>
|
|
304
|
+
);
|
|
305
|
+
mount(nodes);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// Act
|
|
309
|
+
// Let's fake a focusin (this is the event that the anchor gets
|
|
310
|
+
// whether focused directly or a child is focused). We have to
|
|
311
|
+
// fake directly because there's no real browser here handling
|
|
312
|
+
// focus and real events.
|
|
313
|
+
ref && ref.dispatchEvent(new FocusEvent("focusin"));
|
|
314
|
+
|
|
315
|
+
// Assert
|
|
316
|
+
expect(activeState).toBe(true);
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
describe("loses keyboard focus", () => {
|
|
321
|
+
test("active state was not stolen, active is set to false with delay", async () => {
|
|
322
|
+
// Arrange
|
|
323
|
+
let activeState = false;
|
|
324
|
+
const ref = await new Promise((resolve) => {
|
|
325
|
+
const nodes = (
|
|
326
|
+
<TooltipAnchor
|
|
327
|
+
anchorRef={resolve}
|
|
328
|
+
onActiveChanged={(active) => {
|
|
329
|
+
activeState = active;
|
|
330
|
+
}}
|
|
331
|
+
>
|
|
332
|
+
Anchor Text
|
|
333
|
+
</TooltipAnchor>
|
|
334
|
+
);
|
|
335
|
+
mount(nodes);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
ref && ref.dispatchEvent(new FocusEvent("focusin"));
|
|
339
|
+
expect(setTimeout).toHaveBeenLastCalledWith(
|
|
340
|
+
expect.any(Function),
|
|
341
|
+
TooltipAppearanceDelay,
|
|
342
|
+
);
|
|
343
|
+
jest.runOnlyPendingTimers();
|
|
344
|
+
expect(activeState).toBe(true);
|
|
345
|
+
|
|
346
|
+
// Act
|
|
347
|
+
ref && ref.dispatchEvent(new FocusEvent("focusout"));
|
|
348
|
+
expect(activeState).toBe(true);
|
|
349
|
+
expect(setTimeout).toHaveBeenLastCalledWith(
|
|
350
|
+
expect.any(Function),
|
|
351
|
+
TooltipDisappearanceDelay,
|
|
352
|
+
);
|
|
353
|
+
jest.runOnlyPendingTimers();
|
|
354
|
+
|
|
355
|
+
// Assert
|
|
356
|
+
expect(activeState).toBe(false);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
test("active state was not stolen, gives up active state", async () => {
|
|
360
|
+
// Arrange
|
|
361
|
+
const {default: ActiveTracker} = await import(
|
|
362
|
+
"../../util/active-tracker.js"
|
|
363
|
+
);
|
|
364
|
+
// Flow doesn't know this is a mock
|
|
365
|
+
// $FlowFixMe[prop-missing]
|
|
366
|
+
const mockTracker = ActiveTracker.mock.instances[0];
|
|
367
|
+
|
|
368
|
+
let activeState = false;
|
|
369
|
+
const ref = await new Promise((resolve) => {
|
|
370
|
+
const nodes = (
|
|
371
|
+
<TooltipAnchor
|
|
372
|
+
anchorRef={resolve}
|
|
373
|
+
onActiveChanged={(active) => {
|
|
374
|
+
activeState = active;
|
|
375
|
+
}}
|
|
376
|
+
>
|
|
377
|
+
Anchor Text
|
|
378
|
+
</TooltipAnchor>
|
|
379
|
+
);
|
|
380
|
+
mount(nodes);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
ref && ref.dispatchEvent(new FocusEvent("focusin"));
|
|
384
|
+
expect(setTimeout).toHaveBeenLastCalledWith(
|
|
385
|
+
expect.any(Function),
|
|
386
|
+
TooltipAppearanceDelay,
|
|
387
|
+
);
|
|
388
|
+
jest.runOnlyPendingTimers();
|
|
389
|
+
expect(activeState).toBe(true);
|
|
390
|
+
|
|
391
|
+
// Act
|
|
392
|
+
ref && ref.dispatchEvent(new FocusEvent("focusout"));
|
|
393
|
+
expect(activeState).toBe(true);
|
|
394
|
+
expect(setTimeout).toHaveBeenLastCalledWith(
|
|
395
|
+
expect.any(Function),
|
|
396
|
+
TooltipDisappearanceDelay,
|
|
397
|
+
);
|
|
398
|
+
jest.runOnlyPendingTimers();
|
|
399
|
+
|
|
400
|
+
// Assert
|
|
401
|
+
expect(mockTracker.giveup).toHaveBeenCalledTimes(1);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test("active state was stolen, active is set to false immediately", async () => {
|
|
405
|
+
// Arrange
|
|
406
|
+
let wrapper;
|
|
407
|
+
let activeState = false;
|
|
408
|
+
const ref = await new Promise((resolve) => {
|
|
409
|
+
const nodes = (
|
|
410
|
+
<TooltipAnchor
|
|
411
|
+
anchorRef={resolve}
|
|
412
|
+
onActiveChanged={(active) => {
|
|
413
|
+
activeState = active;
|
|
414
|
+
}}
|
|
415
|
+
>
|
|
416
|
+
Anchor Text
|
|
417
|
+
</TooltipAnchor>
|
|
418
|
+
);
|
|
419
|
+
wrapper = mount(nodes);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
ref && ref.dispatchEvent(new FocusEvent("focusin"));
|
|
423
|
+
expect(setTimeout).toHaveBeenLastCalledWith(
|
|
424
|
+
expect.any(Function),
|
|
425
|
+
TooltipAppearanceDelay,
|
|
426
|
+
);
|
|
427
|
+
jest.runOnlyPendingTimers();
|
|
428
|
+
expect(activeState).toBe(true);
|
|
429
|
+
|
|
430
|
+
// Act
|
|
431
|
+
ref && ref.dispatchEvent(new FocusEvent("focusout"));
|
|
432
|
+
wrapper && wrapper.instance().activeStateStolen();
|
|
433
|
+
|
|
434
|
+
// Assert
|
|
435
|
+
expect(activeState).toBe(false);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
test("active state was stolen, so it does not have it to give up", async () => {
|
|
439
|
+
// Arrange
|
|
440
|
+
const {default: ActiveTracker} = await import(
|
|
441
|
+
"../../util/active-tracker.js"
|
|
442
|
+
);
|
|
443
|
+
// Flow doesn't know this is a mock
|
|
444
|
+
// $FlowFixMe[prop-missing]
|
|
445
|
+
const mockTracker = ActiveTracker.mock.instances[0];
|
|
446
|
+
// Arrange
|
|
447
|
+
let wrapper;
|
|
448
|
+
let activeState = false;
|
|
449
|
+
const ref = await new Promise((resolve) => {
|
|
450
|
+
const nodes = (
|
|
451
|
+
<TooltipAnchor
|
|
452
|
+
anchorRef={resolve}
|
|
453
|
+
onActiveChanged={(active) => {
|
|
454
|
+
activeState = active;
|
|
455
|
+
}}
|
|
456
|
+
>
|
|
457
|
+
Anchor Text
|
|
458
|
+
</TooltipAnchor>
|
|
459
|
+
);
|
|
460
|
+
wrapper = mount(nodes);
|
|
461
|
+
});
|
|
462
|
+
ref && ref.dispatchEvent(new FocusEvent("focusin"));
|
|
463
|
+
expect(setTimeout).toHaveBeenLastCalledWith(
|
|
464
|
+
expect.any(Function),
|
|
465
|
+
TooltipAppearanceDelay,
|
|
466
|
+
);
|
|
467
|
+
jest.runOnlyPendingTimers();
|
|
468
|
+
expect(activeState).toBe(true);
|
|
469
|
+
|
|
470
|
+
// Act
|
|
471
|
+
ref && ref.dispatchEvent(new FocusEvent("focusout"));
|
|
472
|
+
wrapper && wrapper.instance().activeStateStolen();
|
|
473
|
+
|
|
474
|
+
// Assert
|
|
475
|
+
expect(mockTracker.giveup).not.toHaveBeenCalled();
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
test("if hovered, remains active", async () => {
|
|
479
|
+
// Arrange
|
|
480
|
+
let activeState = false;
|
|
481
|
+
const ref = await new Promise((resolve) => {
|
|
482
|
+
const nodes = (
|
|
483
|
+
<TooltipAnchor
|
|
484
|
+
anchorRef={resolve}
|
|
485
|
+
onActiveChanged={(active) => {
|
|
486
|
+
activeState = active;
|
|
487
|
+
}}
|
|
488
|
+
>
|
|
489
|
+
Anchor Text
|
|
490
|
+
</TooltipAnchor>
|
|
491
|
+
);
|
|
492
|
+
mount(nodes);
|
|
493
|
+
});
|
|
494
|
+
ref && ref.dispatchEvent(new FocusEvent("focusin"));
|
|
495
|
+
expect(setTimeout).toHaveBeenLastCalledWith(
|
|
496
|
+
expect.any(Function),
|
|
497
|
+
TooltipAppearanceDelay,
|
|
498
|
+
);
|
|
499
|
+
jest.runOnlyPendingTimers();
|
|
500
|
+
// Flow doesn't know we added jest mocks to this
|
|
501
|
+
// $FlowFixMe[prop-missing]
|
|
502
|
+
setTimeout.mockClear();
|
|
503
|
+
ref && ref.dispatchEvent(new MouseEvent("mouseenter"));
|
|
504
|
+
|
|
505
|
+
// Act
|
|
506
|
+
ref && ref.dispatchEvent(new FocusEvent("focusout"));
|
|
507
|
+
|
|
508
|
+
// Assert
|
|
509
|
+
// Make sure that we're not delay hiding as well.
|
|
510
|
+
expect(activeState).toBe(true);
|
|
511
|
+
expect(setTimeout).not.toHaveBeenCalled();
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
describe("is hovered", () => {
|
|
516
|
+
test("active state was not stolen, delays set active", async () => {
|
|
517
|
+
// Arrange
|
|
518
|
+
const {default: ActiveTracker} = await import(
|
|
519
|
+
"../../util/active-tracker.js"
|
|
520
|
+
);
|
|
521
|
+
// Let's tell the tooltip it isn't stealing and therefore it should
|
|
522
|
+
// be using a delay to show the tooltip.
|
|
523
|
+
// Flow doesn't know this is a mock
|
|
524
|
+
// $FlowFixMe[prop-missing]
|
|
525
|
+
const mockTracker = ActiveTracker.mock.instances[0];
|
|
526
|
+
mockTracker.steal.mockImplementationOnce(() => false);
|
|
527
|
+
|
|
528
|
+
let activeState = false;
|
|
529
|
+
const ref = await new Promise((resolve) => {
|
|
530
|
+
const nodes = (
|
|
531
|
+
<TooltipAnchor
|
|
532
|
+
anchorRef={resolve}
|
|
533
|
+
onActiveChanged={(active) => {
|
|
534
|
+
activeState = active;
|
|
535
|
+
}}
|
|
536
|
+
>
|
|
537
|
+
Anchor Text
|
|
538
|
+
</TooltipAnchor>
|
|
539
|
+
);
|
|
540
|
+
mount(nodes);
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
// Act
|
|
544
|
+
ref && ref.dispatchEvent(new MouseEvent("mouseenter"));
|
|
545
|
+
// Check that we didn't go active before the delay
|
|
546
|
+
expect(activeState).toBe(false);
|
|
547
|
+
expect(setTimeout).toHaveBeenLastCalledWith(
|
|
548
|
+
expect.any(Function),
|
|
549
|
+
TooltipAppearanceDelay,
|
|
550
|
+
);
|
|
551
|
+
jest.runOnlyPendingTimers();
|
|
552
|
+
|
|
553
|
+
// Assert
|
|
554
|
+
expect(activeState).toBe(true);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
test("active state was stolen, set active immediately", async () => {
|
|
558
|
+
// Arrange
|
|
559
|
+
const {default: ActiveTracker} = await import(
|
|
560
|
+
"../../util/active-tracker.js"
|
|
561
|
+
);
|
|
562
|
+
// Let's tell the tooltip it is stealing and therefore it should
|
|
563
|
+
// not be using a delay to show the tooltip.
|
|
564
|
+
// Flow doesn't know this is a mock
|
|
565
|
+
// $FlowFixMe[prop-missing]
|
|
566
|
+
const mockTracker = ActiveTracker.mock.instances[0];
|
|
567
|
+
mockTracker.steal.mockImplementationOnce(() => true);
|
|
568
|
+
|
|
569
|
+
let activeState = false;
|
|
570
|
+
const ref = await new Promise((resolve) => {
|
|
571
|
+
const nodes = (
|
|
572
|
+
<TooltipAnchor
|
|
573
|
+
anchorRef={resolve}
|
|
574
|
+
onActiveChanged={(active) => {
|
|
575
|
+
activeState = active;
|
|
576
|
+
}}
|
|
577
|
+
>
|
|
578
|
+
Anchor Text
|
|
579
|
+
</TooltipAnchor>
|
|
580
|
+
);
|
|
581
|
+
mount(nodes);
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
// Act
|
|
585
|
+
ref && ref.dispatchEvent(new MouseEvent("mouseenter"));
|
|
586
|
+
|
|
587
|
+
// Assert
|
|
588
|
+
expect(activeState).toBe(true);
|
|
589
|
+
});
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
describe("is unhovered", () => {
|
|
593
|
+
test("active state was not stolen, active is set to false with delay", async () => {
|
|
594
|
+
// Arrange
|
|
595
|
+
let activeState = false;
|
|
596
|
+
const ref = await new Promise((resolve) => {
|
|
597
|
+
const nodes = (
|
|
598
|
+
<TooltipAnchor
|
|
599
|
+
anchorRef={resolve}
|
|
600
|
+
onActiveChanged={(active) => {
|
|
601
|
+
activeState = active;
|
|
602
|
+
}}
|
|
603
|
+
>
|
|
604
|
+
Anchor Text
|
|
605
|
+
</TooltipAnchor>
|
|
606
|
+
);
|
|
607
|
+
mount(nodes);
|
|
608
|
+
});
|
|
609
|
+
ref && ref.dispatchEvent(new MouseEvent("mouseenter"));
|
|
610
|
+
expect(setTimeout).toHaveBeenLastCalledWith(
|
|
611
|
+
expect.any(Function),
|
|
612
|
+
TooltipAppearanceDelay,
|
|
613
|
+
);
|
|
614
|
+
jest.runOnlyPendingTimers();
|
|
615
|
+
expect(activeState).toBe(true);
|
|
616
|
+
|
|
617
|
+
// Act
|
|
618
|
+
ref && ref.dispatchEvent(new MouseEvent("mouseleave"));
|
|
619
|
+
expect(activeState).toBe(true);
|
|
620
|
+
expect(setTimeout).toHaveBeenLastCalledWith(
|
|
621
|
+
expect.any(Function),
|
|
622
|
+
TooltipDisappearanceDelay,
|
|
623
|
+
);
|
|
624
|
+
jest.runOnlyPendingTimers();
|
|
625
|
+
|
|
626
|
+
// Assert
|
|
627
|
+
expect(activeState).toBe(false);
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
test("active state was not stolen, gives up active state", async () => {
|
|
631
|
+
// Arrange
|
|
632
|
+
const {default: ActiveTracker} = await import(
|
|
633
|
+
"../../util/active-tracker.js"
|
|
634
|
+
);
|
|
635
|
+
// Flow doesn't know this is a mock
|
|
636
|
+
// $FlowFixMe[prop-missing]
|
|
637
|
+
const mockTracker = ActiveTracker.mock.instances[0];
|
|
638
|
+
let activeState = false;
|
|
639
|
+
const ref = await new Promise((resolve) => {
|
|
640
|
+
const nodes = (
|
|
641
|
+
<TooltipAnchor
|
|
642
|
+
anchorRef={resolve}
|
|
643
|
+
onActiveChanged={(active) => {
|
|
644
|
+
activeState = active;
|
|
645
|
+
}}
|
|
646
|
+
>
|
|
647
|
+
Anchor Text
|
|
648
|
+
</TooltipAnchor>
|
|
649
|
+
);
|
|
650
|
+
mount(nodes);
|
|
651
|
+
});
|
|
652
|
+
ref && ref.dispatchEvent(new MouseEvent("mouseenter"));
|
|
653
|
+
expect(setTimeout).toHaveBeenLastCalledWith(
|
|
654
|
+
expect.any(Function),
|
|
655
|
+
TooltipAppearanceDelay,
|
|
656
|
+
);
|
|
657
|
+
jest.runOnlyPendingTimers();
|
|
658
|
+
expect(activeState).toBe(true);
|
|
659
|
+
|
|
660
|
+
// Act
|
|
661
|
+
ref && ref.dispatchEvent(new MouseEvent("mouseleave"));
|
|
662
|
+
expect(activeState).toBe(true);
|
|
663
|
+
expect(setTimeout).toHaveBeenLastCalledWith(
|
|
664
|
+
expect.any(Function),
|
|
665
|
+
TooltipDisappearanceDelay,
|
|
666
|
+
);
|
|
667
|
+
jest.runOnlyPendingTimers();
|
|
668
|
+
|
|
669
|
+
// Assert
|
|
670
|
+
expect(mockTracker.giveup).toHaveBeenCalledTimes(1);
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
test("active state was stolen, active is set to false immediately", async () => {
|
|
674
|
+
// Arrange
|
|
675
|
+
let wrapper;
|
|
676
|
+
let activeState = false;
|
|
677
|
+
const ref = await new Promise((resolve) => {
|
|
678
|
+
const nodes = (
|
|
679
|
+
<TooltipAnchor
|
|
680
|
+
anchorRef={resolve}
|
|
681
|
+
onActiveChanged={(active) => {
|
|
682
|
+
activeState = active;
|
|
683
|
+
}}
|
|
684
|
+
>
|
|
685
|
+
Anchor Text
|
|
686
|
+
</TooltipAnchor>
|
|
687
|
+
);
|
|
688
|
+
wrapper = mount(nodes);
|
|
689
|
+
});
|
|
690
|
+
ref && ref.dispatchEvent(new MouseEvent("mouseenter"));
|
|
691
|
+
expect(setTimeout).toHaveBeenLastCalledWith(
|
|
692
|
+
expect.any(Function),
|
|
693
|
+
TooltipAppearanceDelay,
|
|
694
|
+
);
|
|
695
|
+
jest.runOnlyPendingTimers();
|
|
696
|
+
expect(activeState).toBe(true);
|
|
697
|
+
|
|
698
|
+
// Act
|
|
699
|
+
ref && ref.dispatchEvent(new MouseEvent("mouseleave"));
|
|
700
|
+
wrapper && wrapper.instance().activeStateStolen();
|
|
701
|
+
|
|
702
|
+
// Assert
|
|
703
|
+
expect(activeState).toBe(false);
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
test("active state was stolen, so it does not have it to give up", async () => {
|
|
707
|
+
// Arrange
|
|
708
|
+
const {default: ActiveTracker} = await import(
|
|
709
|
+
"../../util/active-tracker.js"
|
|
710
|
+
);
|
|
711
|
+
// Flow doesn't know this is a mock
|
|
712
|
+
// $FlowFixMe[prop-missing]
|
|
713
|
+
const mockTracker = ActiveTracker.mock.instances[0];
|
|
714
|
+
// Arrange
|
|
715
|
+
let wrapper;
|
|
716
|
+
let activeState = false;
|
|
717
|
+
const ref = await new Promise((resolve) => {
|
|
718
|
+
const nodes = (
|
|
719
|
+
<TooltipAnchor
|
|
720
|
+
anchorRef={resolve}
|
|
721
|
+
onActiveChanged={(active) => {
|
|
722
|
+
activeState = active;
|
|
723
|
+
}}
|
|
724
|
+
>
|
|
725
|
+
Anchor Text
|
|
726
|
+
</TooltipAnchor>
|
|
727
|
+
);
|
|
728
|
+
wrapper = mount(nodes);
|
|
729
|
+
});
|
|
730
|
+
ref && ref.dispatchEvent(new MouseEvent("mouseenter"));
|
|
731
|
+
expect(setTimeout).toHaveBeenLastCalledWith(
|
|
732
|
+
expect.any(Function),
|
|
733
|
+
TooltipAppearanceDelay,
|
|
734
|
+
);
|
|
735
|
+
jest.runOnlyPendingTimers();
|
|
736
|
+
expect(activeState).toBe(true);
|
|
737
|
+
|
|
738
|
+
// Act
|
|
739
|
+
ref && ref.dispatchEvent(new MouseEvent("mouseleave"));
|
|
740
|
+
wrapper && wrapper.instance().activeStateStolen();
|
|
741
|
+
|
|
742
|
+
// Assert
|
|
743
|
+
expect(mockTracker.giveup).not.toHaveBeenCalled();
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
test("if focused, remains active", async () => {
|
|
747
|
+
// Arrange
|
|
748
|
+
let activeState = false;
|
|
749
|
+
const ref = await new Promise((resolve) => {
|
|
750
|
+
const nodes = (
|
|
751
|
+
<TooltipAnchor
|
|
752
|
+
anchorRef={resolve}
|
|
753
|
+
onActiveChanged={(active) => {
|
|
754
|
+
activeState = active;
|
|
755
|
+
}}
|
|
756
|
+
>
|
|
757
|
+
Anchor Text
|
|
758
|
+
</TooltipAnchor>
|
|
759
|
+
);
|
|
760
|
+
mount(nodes);
|
|
761
|
+
});
|
|
762
|
+
ref && ref.dispatchEvent(new MouseEvent("mouseenter"));
|
|
763
|
+
expect(setTimeout).toHaveBeenLastCalledWith(
|
|
764
|
+
expect.any(Function),
|
|
765
|
+
TooltipAppearanceDelay,
|
|
766
|
+
);
|
|
767
|
+
jest.runOnlyPendingTimers();
|
|
768
|
+
// Flow doesn't know we added jest mocks to this
|
|
769
|
+
// $FlowFixMe[prop-missing]
|
|
770
|
+
setTimeout.mockClear();
|
|
771
|
+
ref && ref.dispatchEvent(new FocusEvent("focusin"));
|
|
772
|
+
|
|
773
|
+
// Act
|
|
774
|
+
ref && ref.dispatchEvent(new MouseEvent("mouseleave"));
|
|
775
|
+
|
|
776
|
+
// Assert
|
|
777
|
+
// Make sure that we're not delay hiding as well.
|
|
778
|
+
expect(activeState).toBe(true);
|
|
779
|
+
expect(setTimeout).not.toHaveBeenCalled();
|
|
780
|
+
});
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
describe("dismiss behavior", () => {
|
|
784
|
+
test("subscribes to keydown event on active", async () => {
|
|
785
|
+
// Arrange
|
|
786
|
+
const spy = jest.spyOn(document, "addEventListener");
|
|
787
|
+
const ref = await new Promise((resolve) => {
|
|
788
|
+
const nodes = (
|
|
789
|
+
<TooltipAnchor
|
|
790
|
+
anchorRef={resolve}
|
|
791
|
+
onActiveChanged={() => {}}
|
|
792
|
+
>
|
|
793
|
+
Anchor Text
|
|
794
|
+
</TooltipAnchor>
|
|
795
|
+
);
|
|
796
|
+
mount(nodes);
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
// Act
|
|
800
|
+
ref && ref.dispatchEvent(new MouseEvent("mouseenter"));
|
|
801
|
+
expect(setTimeout).toHaveBeenLastCalledWith(
|
|
802
|
+
expect.any(Function),
|
|
803
|
+
TooltipAppearanceDelay,
|
|
804
|
+
);
|
|
805
|
+
jest.runOnlyPendingTimers();
|
|
806
|
+
|
|
807
|
+
// Assert
|
|
808
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
809
|
+
expect(spy).toHaveBeenLastCalledWith("keyup", expect.any(Function));
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
test("does not subscribe to keydown event if already active", async () => {
|
|
813
|
+
// Arrange
|
|
814
|
+
const spy = jest.spyOn(document, "addEventListener");
|
|
815
|
+
const ref = await new Promise((resolve) => {
|
|
816
|
+
const nodes = (
|
|
817
|
+
<TooltipAnchor
|
|
818
|
+
anchorRef={resolve}
|
|
819
|
+
onActiveChanged={() => {}}
|
|
820
|
+
>
|
|
821
|
+
Anchor Text
|
|
822
|
+
</TooltipAnchor>
|
|
823
|
+
);
|
|
824
|
+
mount(nodes);
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
ref && ref.dispatchEvent(new KeyboardEvent("focusin"));
|
|
828
|
+
expect(setTimeout).toHaveBeenLastCalledWith(
|
|
829
|
+
expect.any(Function),
|
|
830
|
+
TooltipAppearanceDelay,
|
|
831
|
+
);
|
|
832
|
+
jest.runOnlyPendingTimers();
|
|
833
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
834
|
+
expect(spy).toHaveBeenLastCalledWith("keyup", expect.any(Function));
|
|
835
|
+
spy.mockClear();
|
|
836
|
+
|
|
837
|
+
// Act
|
|
838
|
+
ref && ref.dispatchEvent(new MouseEvent("mouseenter"));
|
|
839
|
+
|
|
840
|
+
// Assert
|
|
841
|
+
expect(spy).not.toHaveBeenCalled();
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
test("unsubscribes from keydown event on inactive", async () => {
|
|
845
|
+
// Arrange
|
|
846
|
+
const spy = jest.spyOn(document, "removeEventListener");
|
|
847
|
+
const ref = await new Promise((resolve) => {
|
|
848
|
+
const nodes = (
|
|
849
|
+
<TooltipAnchor
|
|
850
|
+
anchorRef={resolve}
|
|
851
|
+
onActiveChanged={() => {}}
|
|
852
|
+
>
|
|
853
|
+
Anchor Text
|
|
854
|
+
</TooltipAnchor>
|
|
855
|
+
);
|
|
856
|
+
mount(nodes);
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
ref && ref.dispatchEvent(new KeyboardEvent("focusin"));
|
|
860
|
+
expect(setTimeout).toHaveBeenLastCalledWith(
|
|
861
|
+
expect.any(Function),
|
|
862
|
+
TooltipAppearanceDelay,
|
|
863
|
+
);
|
|
864
|
+
jest.runOnlyPendingTimers();
|
|
865
|
+
|
|
866
|
+
// Act
|
|
867
|
+
ref && ref.dispatchEvent(new KeyboardEvent("focusout"));
|
|
868
|
+
expect(setTimeout).toHaveBeenLastCalledWith(
|
|
869
|
+
expect.any(Function),
|
|
870
|
+
TooltipDisappearanceDelay,
|
|
871
|
+
);
|
|
872
|
+
jest.runOnlyPendingTimers();
|
|
873
|
+
|
|
874
|
+
// Assert
|
|
875
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
876
|
+
expect(spy).toHaveBeenLastCalledWith("keyup", expect.any(Function));
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
test("unsubscribes from keydown event on unmount", async () => {
|
|
880
|
+
// Arrange
|
|
881
|
+
let wrapper;
|
|
882
|
+
const spy = jest.spyOn(document, "removeEventListener");
|
|
883
|
+
const ref = await new Promise((resolve) => {
|
|
884
|
+
const nodes = (
|
|
885
|
+
<TooltipAnchor
|
|
886
|
+
anchorRef={resolve}
|
|
887
|
+
onActiveChanged={() => {}}
|
|
888
|
+
>
|
|
889
|
+
Anchor Text
|
|
890
|
+
</TooltipAnchor>
|
|
891
|
+
);
|
|
892
|
+
wrapper = mount(nodes);
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
ref && ref.dispatchEvent(new KeyboardEvent("focusin"));
|
|
896
|
+
expect(setTimeout).toHaveBeenLastCalledWith(
|
|
897
|
+
expect.any(Function),
|
|
898
|
+
TooltipAppearanceDelay,
|
|
899
|
+
);
|
|
900
|
+
jest.runOnlyPendingTimers();
|
|
901
|
+
|
|
902
|
+
// Act
|
|
903
|
+
wrapper && wrapper.unmount();
|
|
904
|
+
|
|
905
|
+
// Assert
|
|
906
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
907
|
+
expect(spy).toHaveBeenLastCalledWith("keyup", expect.any(Function));
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
test("when active, escape dismisses tooltip", async () => {
|
|
911
|
+
// Arrange
|
|
912
|
+
let activeState = false;
|
|
913
|
+
const ref = await new Promise((resolve) => {
|
|
914
|
+
const nodes = (
|
|
915
|
+
<TooltipAnchor
|
|
916
|
+
anchorRef={resolve}
|
|
917
|
+
onActiveChanged={(active) => {
|
|
918
|
+
activeState = active;
|
|
919
|
+
}}
|
|
920
|
+
>
|
|
921
|
+
Anchor Text
|
|
922
|
+
</TooltipAnchor>
|
|
923
|
+
);
|
|
924
|
+
mount(nodes);
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
ref && ref.dispatchEvent(new KeyboardEvent("focusin"));
|
|
928
|
+
expect(setTimeout).toHaveBeenLastCalledWith(
|
|
929
|
+
expect.any(Function),
|
|
930
|
+
TooltipAppearanceDelay,
|
|
931
|
+
);
|
|
932
|
+
jest.runOnlyPendingTimers();
|
|
933
|
+
const event: KeyboardEvent = (document.createEvent("Event"): any);
|
|
934
|
+
event.key = "Escape";
|
|
935
|
+
event.which = 27;
|
|
936
|
+
event.initEvent("keyup", true, true);
|
|
937
|
+
|
|
938
|
+
// Act
|
|
939
|
+
document.dispatchEvent(event);
|
|
940
|
+
expect(setTimeout).toHaveBeenLastCalledWith(
|
|
941
|
+
expect.any(Function),
|
|
942
|
+
TooltipDisappearanceDelay,
|
|
943
|
+
);
|
|
944
|
+
jest.runOnlyPendingTimers();
|
|
945
|
+
|
|
946
|
+
// Assert
|
|
947
|
+
expect(activeState).toBe(false);
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
test("when active, escape stops event propagation", async () => {
|
|
951
|
+
// Arrange
|
|
952
|
+
const ref = await new Promise((resolve) => {
|
|
953
|
+
const nodes = (
|
|
954
|
+
<TooltipAnchor
|
|
955
|
+
anchorRef={resolve}
|
|
956
|
+
onActiveChanged={() => {}}
|
|
957
|
+
>
|
|
958
|
+
Anchor Text
|
|
959
|
+
</TooltipAnchor>
|
|
960
|
+
);
|
|
961
|
+
mount(nodes);
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
ref && ref.dispatchEvent(new KeyboardEvent("focusin"));
|
|
965
|
+
expect(setTimeout).toHaveBeenLastCalledWith(
|
|
966
|
+
expect.any(Function),
|
|
967
|
+
TooltipAppearanceDelay,
|
|
968
|
+
);
|
|
969
|
+
jest.runOnlyPendingTimers();
|
|
970
|
+
const event: KeyboardEvent = (document.createEvent("Event"): any);
|
|
971
|
+
const spyOnStopPropagation = jest.spyOn(event, "stopPropagation");
|
|
972
|
+
event.key = "Escape";
|
|
973
|
+
event.initEvent("keyup", true, true);
|
|
974
|
+
|
|
975
|
+
// Act
|
|
976
|
+
document.dispatchEvent(event);
|
|
977
|
+
expect(setTimeout).toHaveBeenLastCalledWith(
|
|
978
|
+
expect.any(Function),
|
|
979
|
+
TooltipDisappearanceDelay,
|
|
980
|
+
);
|
|
981
|
+
jest.runOnlyPendingTimers();
|
|
982
|
+
|
|
983
|
+
// Assert
|
|
984
|
+
expect(spyOnStopPropagation).toHaveBeenCalled();
|
|
985
|
+
});
|
|
986
|
+
});
|
|
987
|
+
});
|