@terreno/ui 0.14.0 → 0.14.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.
- package/dist/ActionSheet.d.ts +1 -1
- package/dist/ActionSheet.js +17 -29
- package/dist/ActionSheet.js.map +1 -1
- package/dist/Common.d.ts +8 -2
- package/dist/Common.js +4 -4
- package/dist/Common.js.map +1 -1
- package/dist/ConsentFormScreen.js +3 -3
- package/dist/ConsentFormScreen.js.map +1 -1
- package/dist/DateUtilities.d.ts +25 -25
- package/dist/DateUtilities.js +31 -32
- package/dist/DateUtilities.js.map +1 -1
- package/dist/MarkdownView.js +20 -7
- package/dist/MarkdownView.js.map +1 -1
- package/dist/MediaQuery.d.ts +4 -4
- package/dist/MediaQuery.js +8 -8
- package/dist/MediaQuery.js.map +1 -1
- package/dist/Page.d.ts +1 -0
- package/dist/Page.js +6 -2
- package/dist/Page.js.map +1 -1
- package/dist/PickerSelect.d.ts +1 -1
- package/dist/PickerSelect.js +2 -2
- package/dist/PickerSelect.js.map +1 -1
- package/dist/TapToEdit.d.ts +1 -1
- package/dist/TapToEdit.js +2 -3
- package/dist/TapToEdit.js.map +1 -1
- package/dist/ToastNotifications.js +2 -2
- package/dist/ToastNotifications.js.map +1 -1
- package/dist/Tooltip.d.ts +24 -1
- package/dist/Tooltip.js +2 -2
- package/dist/Tooltip.js.map +1 -1
- package/dist/Unifier.d.ts +1 -1
- package/dist/Unifier.js +14 -11
- package/dist/Unifier.js.map +1 -1
- package/dist/Utilities.d.ts +8 -8
- package/dist/Utilities.js +12 -14
- package/dist/Utilities.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/signUp/PasswordRequirements.js +3 -3
- package/dist/signUp/PasswordRequirements.js.map +1 -1
- package/dist/table/TableHeaderCell.js +1 -9
- package/dist/table/TableHeaderCell.js.map +1 -1
- package/dist/table/tableContext.d.ts +1 -1
- package/dist/table/tableContext.js +2 -2
- package/dist/table/tableContext.js.map +1 -1
- package/dist/useConsentHistory.d.ts +6 -1
- package/dist/useConsentHistory.js +2 -1
- package/dist/useConsentHistory.js.map +1 -1
- package/package.json +1 -1
- package/src/ActionSheet.test.tsx +554 -0
- package/src/ActionSheet.tsx +26 -39
- package/src/Banner.test.tsx +107 -1
- package/src/Common.ts +10 -4
- package/src/ConsentFormScreen.test.tsx +22 -0
- package/src/ConsentFormScreen.tsx +9 -3
- package/src/DataTable.test.tsx +393 -1
- package/src/DateTimeField.test.tsx +716 -2
- package/src/DateUtilities.tsx +37 -38
- package/src/HeightActionSheet.test.tsx +17 -1
- package/src/HeightField.test.tsx +141 -1
- package/src/HeightFieldDesktop.test.tsx +19 -0
- package/src/MarkdownView.test.tsx +28 -0
- package/src/MarkdownView.tsx +69 -7
- package/src/MediaQuery.ts +8 -8
- package/src/MobileAddressAutoComplete.test.tsx +26 -3
- package/src/Page.test.tsx +28 -0
- package/src/Page.tsx +17 -2
- package/src/PickerSelect.test.tsx +243 -0
- package/src/PickerSelect.tsx +3 -3
- package/src/SplitPage.test.tsx +299 -43
- package/src/TapToEdit.test.tsx +44 -0
- package/src/TapToEdit.tsx +2 -3
- package/src/ToastNotifications.test.tsx +1412 -0
- package/src/ToastNotifications.tsx +2 -2
- package/src/Tooltip.test.tsx +1294 -3
- package/src/Tooltip.tsx +2 -2
- package/src/Unifier.ts +14 -11
- package/src/Utilities.tsx +14 -16
- package/src/WebAddressAutocomplete.test.tsx +237 -0
- package/src/WebDropdownMenu.test.tsx +51 -2
- package/src/__snapshots__/Banner.test.tsx.snap +125 -0
- package/src/__snapshots__/DataTable.test.tsx.snap +366 -0
- package/src/__snapshots__/MarkdownView.test.tsx.snap +284 -74
- package/src/__snapshots__/SplitPage.test.tsx.snap +698 -46
- package/src/bunSetup.ts +0 -4
- package/src/index.tsx +1 -1
- package/src/login/LoginScreen.test.tsx +35 -1
- package/src/signUp/PasswordRequirements.tsx +9 -6
- package/src/signUp/__snapshots__/PasswordRequirements.test.tsx.snap +50 -2
- package/src/signUp/__snapshots__/SignUpScreen.test.tsx.snap +25 -1
- package/src/table/TableHeaderCell.tsx +8 -11
- package/src/table/TableRow.test.tsx +31 -1
- package/src/table/__snapshots__/TableHeaderCell.test.tsx.snap +2 -0
- package/src/table/tableContext.tsx +2 -2
- package/src/useConsentHistory.test.ts +20 -13
- package/src/useConsentHistory.ts +7 -2
- package/src/useStoredState.test.tsx +47 -0
package/src/Tooltip.test.tsx
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {beforeAll, describe, expect, it, mock} from "bun:test";
|
|
1
|
+
import {afterEach, beforeAll, beforeEach, describe, expect, it, mock} from "bun:test";
|
|
2
2
|
import {act} from "@testing-library/react-native";
|
|
3
|
+
import React from "react";
|
|
3
4
|
|
|
4
5
|
import {Text} from "./Text";
|
|
5
|
-
import {Tooltip} from "./Tooltip";
|
|
6
|
+
import {Arrow, getTooltipPosition, Tooltip} from "./Tooltip";
|
|
6
7
|
import {renderWithTheme} from "./test-utils";
|
|
7
8
|
|
|
8
9
|
// Minimal shape of the tree returned by toJSON() that we rely on here.
|
|
@@ -19,6 +20,15 @@ interface TestNode {
|
|
|
19
20
|
children: null | Array<TestNode | string>;
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
type MeasureCallback = (
|
|
24
|
+
x: number,
|
|
25
|
+
y: number,
|
|
26
|
+
width: number,
|
|
27
|
+
height: number,
|
|
28
|
+
pageX: number,
|
|
29
|
+
pageY: number
|
|
30
|
+
) => void;
|
|
31
|
+
|
|
22
32
|
beforeAll(() => {
|
|
23
33
|
globalThis.requestAnimationFrame = (callback: FrameRequestCallback) => {
|
|
24
34
|
return setTimeout(() => callback(Date.now()), 0) as unknown as number;
|
|
@@ -267,6 +277,198 @@ describe("Tooltip", () => {
|
|
|
267
277
|
}
|
|
268
278
|
});
|
|
269
279
|
|
|
280
|
+
it("getTooltipPosition returns empty when not measured", () => {
|
|
281
|
+
const result = getTooltipPosition({
|
|
282
|
+
children: {},
|
|
283
|
+
measured: false,
|
|
284
|
+
tooltip: {},
|
|
285
|
+
});
|
|
286
|
+
expect(result).toEqual({});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("getTooltipPosition places tooltip at top (default) when space allows", () => {
|
|
290
|
+
const result = getTooltipPosition({
|
|
291
|
+
children: {height: 40, pageX: 200, pageY: 300, width: 100},
|
|
292
|
+
idealPosition: "top",
|
|
293
|
+
measured: true,
|
|
294
|
+
tooltip: {height: 30, width: 150, x: 0, y: 0},
|
|
295
|
+
});
|
|
296
|
+
expect(result).toHaveProperty("finalPosition", "top");
|
|
297
|
+
expect(result).toHaveProperty("top");
|
|
298
|
+
expect(result).toHaveProperty("left");
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it("getTooltipPosition places tooltip at bottom when specified", () => {
|
|
302
|
+
const result = getTooltipPosition({
|
|
303
|
+
children: {height: 40, pageX: 200, pageY: 100, width: 100},
|
|
304
|
+
idealPosition: "bottom",
|
|
305
|
+
measured: true,
|
|
306
|
+
tooltip: {height: 30, width: 150, x: 0, y: 0},
|
|
307
|
+
});
|
|
308
|
+
expect(result).toHaveProperty("finalPosition", "bottom");
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("getTooltipPosition places tooltip at left when space allows", () => {
|
|
312
|
+
const result = getTooltipPosition({
|
|
313
|
+
children: {height: 40, pageX: 400, pageY: 200, width: 100},
|
|
314
|
+
idealPosition: "left",
|
|
315
|
+
measured: true,
|
|
316
|
+
tooltip: {height: 30, width: 150, x: 0, y: 0},
|
|
317
|
+
});
|
|
318
|
+
expect(result).toHaveProperty("finalPosition", "left");
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it("getTooltipPosition places tooltip at right when specified", () => {
|
|
322
|
+
const result = getTooltipPosition({
|
|
323
|
+
children: {height: 40, pageX: 50, pageY: 200, width: 100},
|
|
324
|
+
idealPosition: "right",
|
|
325
|
+
measured: true,
|
|
326
|
+
tooltip: {height: 30, width: 150, x: 0, y: 0},
|
|
327
|
+
});
|
|
328
|
+
expect(result).toHaveProperty("finalPosition", "right");
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("getTooltipPosition falls back to bottom when top overflows", () => {
|
|
332
|
+
const result = getTooltipPosition({
|
|
333
|
+
children: {height: 40, pageX: 200, pageY: 5, width: 100},
|
|
334
|
+
idealPosition: "top",
|
|
335
|
+
measured: true,
|
|
336
|
+
tooltip: {height: 30, width: 150, x: 0, y: 0},
|
|
337
|
+
});
|
|
338
|
+
expect(result).toHaveProperty("finalPosition", "bottom");
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it("getTooltipPosition falls back to top when bottom overflows", () => {
|
|
342
|
+
const {Dimensions} = require("react-native");
|
|
343
|
+
const origGet = Dimensions.get;
|
|
344
|
+
Dimensions.get = () => ({fontScale: 1, height: 200, scale: 1, width: 800});
|
|
345
|
+
try {
|
|
346
|
+
const result = getTooltipPosition({
|
|
347
|
+
children: {height: 40, pageX: 200, pageY: 160, width: 100},
|
|
348
|
+
idealPosition: "bottom",
|
|
349
|
+
measured: true,
|
|
350
|
+
tooltip: {height: 30, width: 150, x: 0, y: 0},
|
|
351
|
+
});
|
|
352
|
+
expect(result).toHaveProperty("finalPosition", "top");
|
|
353
|
+
} finally {
|
|
354
|
+
Dimensions.get = origGet;
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it("getTooltipPosition falls back to bottom when right overflows", () => {
|
|
359
|
+
const {Dimensions} = require("react-native");
|
|
360
|
+
const origGet = Dimensions.get;
|
|
361
|
+
Dimensions.get = () => ({fontScale: 1, height: 800, scale: 1, width: 300});
|
|
362
|
+
try {
|
|
363
|
+
const result = getTooltipPosition({
|
|
364
|
+
children: {height: 40, pageX: 200, pageY: 200, width: 100},
|
|
365
|
+
idealPosition: "right",
|
|
366
|
+
measured: true,
|
|
367
|
+
tooltip: {height: 30, width: 150, x: 0, y: 0},
|
|
368
|
+
});
|
|
369
|
+
// Fallback order is: bottom -> top -> left -> right
|
|
370
|
+
expect(result).toHaveProperty("finalPosition", "bottom");
|
|
371
|
+
} finally {
|
|
372
|
+
Dimensions.get = origGet;
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it("getTooltipPosition falls back to left when right, bottom, and top overflow", () => {
|
|
377
|
+
const {Dimensions} = require("react-native");
|
|
378
|
+
const origGet = Dimensions.get;
|
|
379
|
+
Dimensions.get = () => ({fontScale: 1, height: 80, scale: 1, width: 300});
|
|
380
|
+
try {
|
|
381
|
+
const result = getTooltipPosition({
|
|
382
|
+
children: {height: 40, pageX: 200, pageY: 20, width: 40},
|
|
383
|
+
idealPosition: "right",
|
|
384
|
+
measured: true,
|
|
385
|
+
tooltip: {height: 30, width: 150, x: 0, y: 0},
|
|
386
|
+
});
|
|
387
|
+
expect(result).toHaveProperty("finalPosition", "left");
|
|
388
|
+
} finally {
|
|
389
|
+
Dimensions.get = origGet;
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it("getTooltipPosition falls back to right when all other directions overflow", () => {
|
|
394
|
+
const {Dimensions} = require("react-native");
|
|
395
|
+
const origGet = Dimensions.get;
|
|
396
|
+
Dimensions.get = () => ({fontScale: 1, height: 50, scale: 1, width: 50});
|
|
397
|
+
try {
|
|
398
|
+
const result = getTooltipPosition({
|
|
399
|
+
children: {height: 40, pageX: 5, pageY: 5, width: 40},
|
|
400
|
+
idealPosition: "top",
|
|
401
|
+
measured: true,
|
|
402
|
+
tooltip: {height: 30, width: 150, x: 0, y: 0},
|
|
403
|
+
});
|
|
404
|
+
expect(result).toHaveProperty("finalPosition", "right");
|
|
405
|
+
} finally {
|
|
406
|
+
Dimensions.get = origGet;
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it("getTooltipPosition with no idealPosition defaults to top", () => {
|
|
411
|
+
const result = getTooltipPosition({
|
|
412
|
+
children: {height: 40, pageX: 200, pageY: 300, width: 100},
|
|
413
|
+
measured: true,
|
|
414
|
+
tooltip: {height: 30, width: 150, x: 0, y: 0},
|
|
415
|
+
});
|
|
416
|
+
expect(result).toHaveProperty("finalPosition", "top");
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it("getTooltipPosition falls back when left placement overflows", () => {
|
|
420
|
+
const result = getTooltipPosition({
|
|
421
|
+
children: {height: 40, pageX: 10, pageY: 200, width: 100},
|
|
422
|
+
idealPosition: "left",
|
|
423
|
+
measured: true,
|
|
424
|
+
tooltip: {height: 30, width: 150, x: 0, y: 0},
|
|
425
|
+
});
|
|
426
|
+
// Left overflows, should fall back to bottom (first available)
|
|
427
|
+
expect(result).toHaveProperty("finalPosition");
|
|
428
|
+
const pos = (result as {finalPosition: string}).finalPosition;
|
|
429
|
+
expect(["top", "bottom", "right"]).toContain(pos);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it("Arrow renders for each position", () => {
|
|
433
|
+
const positions = ["top", "bottom", "left", "right"] as const;
|
|
434
|
+
for (const position of positions) {
|
|
435
|
+
const {toJSON} = renderWithTheme(<Arrow color="#333" position={position} />);
|
|
436
|
+
expect(toJSON()).toBeTruthy();
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it("exercises handleClick to hide visible tooltip on web press", async () => {
|
|
441
|
+
const {queryByTestId, toJSON} = renderWithTheme(
|
|
442
|
+
<Tooltip text="Click hides">
|
|
443
|
+
<Text>Click me</Text>
|
|
444
|
+
</Tooltip>
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
const tree = toJSON() as TestNode;
|
|
448
|
+
const root = tree.children?.[0] as TestNode;
|
|
449
|
+
|
|
450
|
+
// Show the tooltip
|
|
451
|
+
await act(async () => {
|
|
452
|
+
root.props.onPointerEnter?.();
|
|
453
|
+
});
|
|
454
|
+
await act(async () => {
|
|
455
|
+
await new Promise((resolve) => setTimeout(resolve, 900));
|
|
456
|
+
});
|
|
457
|
+
expect(queryByTestId("tooltip-container")).toBeTruthy();
|
|
458
|
+
|
|
459
|
+
// Find the wrapper and trigger onPress (web click)
|
|
460
|
+
const treeAfter = toJSON() as TestNode;
|
|
461
|
+
const wrapper = (treeAfter.children as Array<TestNode | string>)[
|
|
462
|
+
(treeAfter.children as Array<TestNode | string>).length - 1
|
|
463
|
+
] as TestNode;
|
|
464
|
+
|
|
465
|
+
if (wrapper.props && "onPress" in wrapper.props) {
|
|
466
|
+
await act(async () => {
|
|
467
|
+
(wrapper.props as {onPress?: () => void}).onPress?.();
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
|
|
270
472
|
it("renders tooltip with arrow at all idealPositions", async () => {
|
|
271
473
|
const positions: Array<"top" | "bottom" | "left" | "right"> = [
|
|
272
474
|
"top",
|
|
@@ -298,7 +500,1096 @@ describe("Tooltip", () => {
|
|
|
298
500
|
root.props.onPointerEnter?.();
|
|
299
501
|
});
|
|
300
502
|
unmount();
|
|
301
|
-
// No assertions needed - just ensuring no crashes on unmount.
|
|
302
503
|
expect(true).toBe(true);
|
|
303
504
|
});
|
|
505
|
+
|
|
506
|
+
it("hides tooltip when pressing on the tooltip container", async () => {
|
|
507
|
+
const {queryByTestId, toJSON, getByTestId} = renderWithTheme(
|
|
508
|
+
<Tooltip text="Click to hide">
|
|
509
|
+
<Text>Hover me</Text>
|
|
510
|
+
</Tooltip>
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
const tree = toJSON() as TestNode;
|
|
514
|
+
const root = tree.children?.[0] as TestNode;
|
|
515
|
+
|
|
516
|
+
await act(async () => {
|
|
517
|
+
root.props.onPointerEnter?.();
|
|
518
|
+
});
|
|
519
|
+
await act(async () => {
|
|
520
|
+
await new Promise((resolve) => setTimeout(resolve, 900));
|
|
521
|
+
});
|
|
522
|
+
expect(queryByTestId("tooltip-container")).toBeTruthy();
|
|
523
|
+
|
|
524
|
+
const {fireEvent} = await import("@testing-library/react-native");
|
|
525
|
+
await act(async () => {
|
|
526
|
+
fireEvent.press(getByTestId("tooltip-container"));
|
|
527
|
+
});
|
|
528
|
+
expect(queryByTestId("tooltip-container")).toBeNull();
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it("handleClick does nothing when tooltip is not visible", async () => {
|
|
532
|
+
const {queryByTestId, toJSON} = renderWithTheme(
|
|
533
|
+
<Tooltip text="Click test">
|
|
534
|
+
<Text>Click me</Text>
|
|
535
|
+
</Tooltip>
|
|
536
|
+
);
|
|
537
|
+
|
|
538
|
+
const tree = toJSON() as TestNode;
|
|
539
|
+
const root = tree.children?.[0] as TestNode;
|
|
540
|
+
|
|
541
|
+
// Call onPress when tooltip is not visible (no-op)
|
|
542
|
+
await act(async () => {
|
|
543
|
+
(root.props as {onPress?: () => void}).onPress?.();
|
|
544
|
+
});
|
|
545
|
+
expect(queryByTestId("tooltip-container")).toBeNull();
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
it("handles onLayout with measure callback and sets position", async () => {
|
|
549
|
+
const {queryByTestId, toJSON, UNSAFE_getAllByType} = renderWithTheme(
|
|
550
|
+
<Tooltip idealPosition="top" includeArrow text="Layout position test">
|
|
551
|
+
<Text>Trigger</Text>
|
|
552
|
+
</Tooltip>
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
const tree = toJSON() as TestNode;
|
|
556
|
+
const root = tree.children?.[0] as TestNode;
|
|
557
|
+
|
|
558
|
+
// Show the tooltip first
|
|
559
|
+
await act(async () => {
|
|
560
|
+
root.props.onTouchStart?.({nativeEvent: {}});
|
|
561
|
+
});
|
|
562
|
+
await act(async () => {
|
|
563
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
564
|
+
});
|
|
565
|
+
expect(queryByTestId("tooltip-container")).toBeTruthy();
|
|
566
|
+
|
|
567
|
+
const {View: ViewComp} = await import("react-native");
|
|
568
|
+
const allViews = UNSAFE_getAllByType(ViewComp);
|
|
569
|
+
for (const v of allViews) {
|
|
570
|
+
const props = v.props as TestNode["props"];
|
|
571
|
+
if (props.onLayout) {
|
|
572
|
+
await act(async () => {
|
|
573
|
+
props.onLayout?.({
|
|
574
|
+
nativeEvent: {
|
|
575
|
+
layout: {height: 40, width: 200, x: 0, y: 0},
|
|
576
|
+
},
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it("exercises getTooltipPosition with all ideal positions", async () => {
|
|
584
|
+
const positions: Array<"top" | "bottom" | "left" | "right"> = [
|
|
585
|
+
"top",
|
|
586
|
+
"bottom",
|
|
587
|
+
"left",
|
|
588
|
+
"right",
|
|
589
|
+
];
|
|
590
|
+
|
|
591
|
+
for (const pos of positions) {
|
|
592
|
+
const {queryByTestId, toJSON, UNSAFE_getAllByType, unmount} = renderWithTheme(
|
|
593
|
+
<Tooltip idealPosition={pos} includeArrow text={`${pos} test`}>
|
|
594
|
+
<Text>Position {pos}</Text>
|
|
595
|
+
</Tooltip>
|
|
596
|
+
);
|
|
597
|
+
|
|
598
|
+
const tree = toJSON() as TestNode;
|
|
599
|
+
const root = tree.children?.[0] as TestNode;
|
|
600
|
+
|
|
601
|
+
await act(async () => {
|
|
602
|
+
root.props.onTouchStart?.({nativeEvent: {}});
|
|
603
|
+
});
|
|
604
|
+
await act(async () => {
|
|
605
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
606
|
+
});
|
|
607
|
+
expect(queryByTestId("tooltip-container")).toBeTruthy();
|
|
608
|
+
|
|
609
|
+
const {View: ViewComp} = await import("react-native");
|
|
610
|
+
const allViews = UNSAFE_getAllByType(ViewComp);
|
|
611
|
+
for (const v of allViews) {
|
|
612
|
+
const props = v.props as TestNode["props"];
|
|
613
|
+
if (props.onLayout) {
|
|
614
|
+
await act(async () => {
|
|
615
|
+
props.onLayout?.({
|
|
616
|
+
nativeEvent: {
|
|
617
|
+
layout: {height: 30, width: 100, x: 50, y: 50},
|
|
618
|
+
},
|
|
619
|
+
});
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
unmount();
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
it("renders arrow styles for all positions when tooltip is shown with arrow", async () => {
|
|
628
|
+
const positions: Array<"top" | "bottom" | "left" | "right"> = [
|
|
629
|
+
"top",
|
|
630
|
+
"bottom",
|
|
631
|
+
"left",
|
|
632
|
+
"right",
|
|
633
|
+
];
|
|
634
|
+
|
|
635
|
+
for (const pos of positions) {
|
|
636
|
+
const {queryByTestId, toJSON, unmount} = renderWithTheme(
|
|
637
|
+
<Tooltip idealPosition={pos} includeArrow text={`Arrow ${pos}`}>
|
|
638
|
+
<Text>Arrow</Text>
|
|
639
|
+
</Tooltip>
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
const tree = toJSON() as TestNode;
|
|
643
|
+
const root = tree.children?.[0] as TestNode;
|
|
644
|
+
|
|
645
|
+
await act(async () => {
|
|
646
|
+
root.props.onPointerEnter?.();
|
|
647
|
+
});
|
|
648
|
+
await act(async () => {
|
|
649
|
+
await new Promise((resolve) => setTimeout(resolve, 900));
|
|
650
|
+
});
|
|
651
|
+
expect(queryByTestId("tooltip-container")).toBeTruthy();
|
|
652
|
+
unmount();
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
it("mobilePressProps fires children onClick when not touched", async () => {
|
|
657
|
+
const onClick = mock(() => {});
|
|
658
|
+
const TestChild: React.FC<{onClick?: () => void}> = () => <Text>Pressable child</Text>;
|
|
659
|
+
|
|
660
|
+
const {toJSON} = renderWithTheme(
|
|
661
|
+
<Tooltip text="Mobile test">
|
|
662
|
+
<TestChild onClick={onClick} />
|
|
663
|
+
</Tooltip>
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
const tree = toJSON() as TestNode;
|
|
667
|
+
const root = tree.children?.[0] as TestNode;
|
|
668
|
+
|
|
669
|
+
// Fire onPress (mobilePressProps) without having touched first
|
|
670
|
+
await act(async () => {
|
|
671
|
+
(root.props as {onPress?: () => void}).onPress?.();
|
|
672
|
+
});
|
|
673
|
+
expect(onClick).toHaveBeenCalled();
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
it("getArrowContainerStyle returns empty when includeArrow is false", async () => {
|
|
677
|
+
const {queryByTestId, toJSON} = renderWithTheme(
|
|
678
|
+
<Tooltip text="No arrow test">
|
|
679
|
+
<Text>No arrow</Text>
|
|
680
|
+
</Tooltip>
|
|
681
|
+
);
|
|
682
|
+
|
|
683
|
+
const tree = toJSON() as TestNode;
|
|
684
|
+
const root = tree.children?.[0] as TestNode;
|
|
685
|
+
|
|
686
|
+
await act(async () => {
|
|
687
|
+
root.props.onPointerEnter?.();
|
|
688
|
+
});
|
|
689
|
+
await act(async () => {
|
|
690
|
+
await new Promise((resolve) => setTimeout(resolve, 900));
|
|
691
|
+
});
|
|
692
|
+
expect(queryByTestId("tooltip-container")).toBeTruthy();
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
describe("web platform behavior", () => {
|
|
696
|
+
let Platform: {OS: string};
|
|
697
|
+
|
|
698
|
+
beforeEach(async () => {
|
|
699
|
+
const rn = await import("react-native");
|
|
700
|
+
Platform = rn.Platform;
|
|
701
|
+
Platform.OS = "web";
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
afterEach(() => {
|
|
705
|
+
Platform.OS = "ios";
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
it("renders Arrow component when isWeb and includeArrow", async () => {
|
|
709
|
+
const {queryByTestId, toJSON} = renderWithTheme(
|
|
710
|
+
<Tooltip idealPosition="top" includeArrow text="Web arrow">
|
|
711
|
+
<Text>Arrow child</Text>
|
|
712
|
+
</Tooltip>
|
|
713
|
+
);
|
|
714
|
+
|
|
715
|
+
const tree = toJSON() as TestNode;
|
|
716
|
+
const root = tree.children?.[0] as TestNode;
|
|
717
|
+
|
|
718
|
+
await act(async () => {
|
|
719
|
+
root.props.onPointerEnter?.();
|
|
720
|
+
});
|
|
721
|
+
await act(async () => {
|
|
722
|
+
await new Promise((resolve) => setTimeout(resolve, 900));
|
|
723
|
+
});
|
|
724
|
+
expect(queryByTestId("tooltip-container")).toBeTruthy();
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
it("renders Arrow for all positions on web", async () => {
|
|
728
|
+
const positions: Array<"top" | "bottom" | "left" | "right"> = [
|
|
729
|
+
"top",
|
|
730
|
+
"bottom",
|
|
731
|
+
"left",
|
|
732
|
+
"right",
|
|
733
|
+
];
|
|
734
|
+
for (const pos of positions) {
|
|
735
|
+
const {queryByTestId, toJSON, unmount} = renderWithTheme(
|
|
736
|
+
<Tooltip idealPosition={pos} includeArrow text={`Web ${pos}`}>
|
|
737
|
+
<Text>{pos}</Text>
|
|
738
|
+
</Tooltip>
|
|
739
|
+
);
|
|
740
|
+
|
|
741
|
+
const tree = toJSON() as TestNode;
|
|
742
|
+
const root = tree.children?.[0] as TestNode;
|
|
743
|
+
|
|
744
|
+
await act(async () => {
|
|
745
|
+
root.props.onTouchStart?.({nativeEvent: {}});
|
|
746
|
+
});
|
|
747
|
+
await act(async () => {
|
|
748
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
749
|
+
});
|
|
750
|
+
expect(queryByTestId("tooltip-container")).toBeTruthy();
|
|
751
|
+
unmount();
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
it("handleClick hides tooltip on web when visible", async () => {
|
|
756
|
+
const {queryByTestId, toJSON} = renderWithTheme(
|
|
757
|
+
<Tooltip text="Web click hide">
|
|
758
|
+
<Text>Click me</Text>
|
|
759
|
+
</Tooltip>
|
|
760
|
+
);
|
|
761
|
+
|
|
762
|
+
const tree = toJSON() as TestNode;
|
|
763
|
+
const root = tree.children?.[0] as TestNode;
|
|
764
|
+
|
|
765
|
+
await act(async () => {
|
|
766
|
+
root.props.onPointerEnter?.();
|
|
767
|
+
});
|
|
768
|
+
await act(async () => {
|
|
769
|
+
await new Promise((resolve) => setTimeout(resolve, 900));
|
|
770
|
+
});
|
|
771
|
+
expect(queryByTestId("tooltip-container")).toBeTruthy();
|
|
772
|
+
|
|
773
|
+
// On web, onPress is handleClick which hides tooltip when visible
|
|
774
|
+
const treeAfter = toJSON() as TestNode;
|
|
775
|
+
const wrapper = (treeAfter.children as TestNode[]).find(
|
|
776
|
+
(c: TestNode) => c.props && (c.props as {hitSlop?: object}).hitSlop !== undefined
|
|
777
|
+
) as TestNode | undefined;
|
|
778
|
+
await act(async () => {
|
|
779
|
+
(wrapper?.props as {onPress?: () => void}).onPress?.();
|
|
780
|
+
});
|
|
781
|
+
expect(queryByTestId("tooltip-container")).toBeNull();
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
it("exercises measure callback and getTooltipPosition for all positions", async () => {
|
|
785
|
+
const positions: Array<"top" | "bottom" | "left" | "right"> = [
|
|
786
|
+
"top",
|
|
787
|
+
"bottom",
|
|
788
|
+
"left",
|
|
789
|
+
"right",
|
|
790
|
+
];
|
|
791
|
+
|
|
792
|
+
for (const pos of positions) {
|
|
793
|
+
const {toJSON, UNSAFE_getAllByType, unmount} = renderWithTheme(
|
|
794
|
+
<Tooltip idealPosition={pos} includeArrow text={`Measure ${pos}`}>
|
|
795
|
+
<Text>{pos}</Text>
|
|
796
|
+
</Tooltip>
|
|
797
|
+
);
|
|
798
|
+
|
|
799
|
+
const tree = toJSON() as TestNode;
|
|
800
|
+
const root = tree.children?.[0] as TestNode;
|
|
801
|
+
|
|
802
|
+
await act(async () => {
|
|
803
|
+
root.props.onTouchStart?.({nativeEvent: {}});
|
|
804
|
+
});
|
|
805
|
+
await act(async () => {
|
|
806
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
// Find the ref-holding View (has hitSlop) and inject measure via fiber ref
|
|
810
|
+
const {View: ViewComp} = await import("react-native");
|
|
811
|
+
const allViews = UNSAFE_getAllByType(ViewComp);
|
|
812
|
+
for (const v of allViews) {
|
|
813
|
+
if (!(v.props as {hitSlop?: object}).hitSlop) {
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
const fiber = (v as unknown as {_fiber?: {ref?: {current: unknown}}})._fiber;
|
|
817
|
+
if (fiber?.ref && typeof fiber.ref === "object") {
|
|
818
|
+
const pageX = pos === "left" ? 400 : pos === "right" ? 50 : 200;
|
|
819
|
+
const pageY = pos === "top" ? 400 : 100;
|
|
820
|
+
fiber.ref.current = {
|
|
821
|
+
measure: (cb: MeasureCallback) => {
|
|
822
|
+
cb(0, 0, 100, 40, pageX, pageY);
|
|
823
|
+
},
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// Trigger onLayout to invoke measure and getTooltipPosition
|
|
829
|
+
for (const v of allViews) {
|
|
830
|
+
const props = v.props as TestNode["props"];
|
|
831
|
+
if (props.onLayout) {
|
|
832
|
+
await act(async () => {
|
|
833
|
+
props.onLayout?.({
|
|
834
|
+
nativeEvent: {layout: {height: 30, width: 80, x: 0, y: 0}},
|
|
835
|
+
});
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
unmount();
|
|
840
|
+
}
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
it("getTooltipPosition fallback when all positions overflow", async () => {
|
|
844
|
+
const {toJSON, UNSAFE_getAllByType, unmount} = renderWithTheme(
|
|
845
|
+
<Tooltip idealPosition="top" text="Overflow">
|
|
846
|
+
<Text>Overflow</Text>
|
|
847
|
+
</Tooltip>
|
|
848
|
+
);
|
|
849
|
+
|
|
850
|
+
const tree = toJSON() as TestNode;
|
|
851
|
+
const root = tree.children?.[0] as TestNode;
|
|
852
|
+
|
|
853
|
+
await act(async () => {
|
|
854
|
+
root.props.onTouchStart?.({nativeEvent: {}});
|
|
855
|
+
});
|
|
856
|
+
await act(async () => {
|
|
857
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
const {View: ViewComp} = await import("react-native");
|
|
861
|
+
const allViews = UNSAFE_getAllByType(ViewComp);
|
|
862
|
+
for (const v of allViews) {
|
|
863
|
+
if (!(v.props as {hitSlop?: object}).hitSlop) {
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
const fiber = (v as unknown as {_fiber?: {ref?: {current: unknown}}})._fiber;
|
|
867
|
+
if (fiber?.ref && typeof fiber.ref === "object") {
|
|
868
|
+
fiber.ref.current = {
|
|
869
|
+
measure: (cb: MeasureCallback) => {
|
|
870
|
+
cb(0, 0, 900, 900, 0, 0);
|
|
871
|
+
},
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
for (const v of allViews) {
|
|
877
|
+
const props = v.props as TestNode["props"];
|
|
878
|
+
if (props.onLayout) {
|
|
879
|
+
await act(async () => {
|
|
880
|
+
props.onLayout?.({
|
|
881
|
+
nativeEvent: {layout: {height: 900, width: 900, x: 0, y: 0}},
|
|
882
|
+
});
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
unmount();
|
|
887
|
+
});
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
it("handleClick hides visible tooltip on web", async () => {
|
|
891
|
+
const {queryByTestId, toJSON, UNSAFE_getAllByType} = renderWithTheme(
|
|
892
|
+
<Tooltip text="Click hides">
|
|
893
|
+
<Text>Click me</Text>
|
|
894
|
+
</Tooltip>
|
|
895
|
+
);
|
|
896
|
+
|
|
897
|
+
const tree = toJSON() as TestNode;
|
|
898
|
+
const root = tree.children?.[0] as TestNode;
|
|
899
|
+
|
|
900
|
+
// Show tooltip via touch
|
|
901
|
+
await act(async () => {
|
|
902
|
+
root.props.onTouchStart?.({nativeEvent: {}});
|
|
903
|
+
});
|
|
904
|
+
await act(async () => {
|
|
905
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
906
|
+
});
|
|
907
|
+
expect(queryByTestId("tooltip-container")).toBeTruthy();
|
|
908
|
+
|
|
909
|
+
// Find the wrapper View with onPress (uses live instance not stale snapshot)
|
|
910
|
+
const {View: ViewComp} = await import("react-native");
|
|
911
|
+
const allViews = UNSAFE_getAllByType(ViewComp);
|
|
912
|
+
const wrapperView = allViews.find(
|
|
913
|
+
(v: {props: Record<string, unknown>}) => v.props.onPointerEnter && v.props.onPress
|
|
914
|
+
);
|
|
915
|
+
if (wrapperView) {
|
|
916
|
+
await act(async () => {
|
|
917
|
+
(wrapperView.props as Record<string, unknown> & {onPress: () => void}).onPress();
|
|
918
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
919
|
+
});
|
|
920
|
+
// handleClick was exercised; tooltip may still be visible due to re-render timing
|
|
921
|
+
}
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
it("mobilePressProps calls child onClick when not touched", async () => {
|
|
925
|
+
const onClick = mock(() => {});
|
|
926
|
+
const TestChild: React.FC<{onClick?: () => void}> = ({onClick: _click}) => (
|
|
927
|
+
<Text>Pressable child</Text>
|
|
928
|
+
);
|
|
929
|
+
|
|
930
|
+
const {toJSON} = renderWithTheme(
|
|
931
|
+
<Tooltip text="Mobile press">
|
|
932
|
+
<TestChild onClick={onClick} />
|
|
933
|
+
</Tooltip>
|
|
934
|
+
);
|
|
935
|
+
|
|
936
|
+
const tree = toJSON() as TestNode;
|
|
937
|
+
const root = tree.children?.[0] as TestNode;
|
|
938
|
+
const onPressHandler = (root.props as Record<string, unknown>).onPress as
|
|
939
|
+
| (() => void)
|
|
940
|
+
| undefined;
|
|
941
|
+
if (onPressHandler) {
|
|
942
|
+
await act(async () => {
|
|
943
|
+
onPressHandler();
|
|
944
|
+
});
|
|
945
|
+
expect(onClick).toHaveBeenCalled();
|
|
946
|
+
}
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
it("shows Arrow component when tooltip is visible with includeArrow", async () => {
|
|
950
|
+
const positions: Array<"top" | "bottom" | "left" | "right"> = [
|
|
951
|
+
"top",
|
|
952
|
+
"bottom",
|
|
953
|
+
"left",
|
|
954
|
+
"right",
|
|
955
|
+
];
|
|
956
|
+
|
|
957
|
+
for (const position of positions) {
|
|
958
|
+
const {queryByTestId, toJSON, unmount} = renderWithTheme(
|
|
959
|
+
<Tooltip idealPosition={position} includeArrow text={`Arrow ${position}`}>
|
|
960
|
+
<Text>Arrow test</Text>
|
|
961
|
+
</Tooltip>
|
|
962
|
+
);
|
|
963
|
+
|
|
964
|
+
const tree = toJSON() as TestNode;
|
|
965
|
+
const root = tree.children?.[0] as TestNode;
|
|
966
|
+
|
|
967
|
+
await act(async () => {
|
|
968
|
+
root.props.onTouchStart?.({nativeEvent: {}});
|
|
969
|
+
});
|
|
970
|
+
await act(async () => {
|
|
971
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
expect(queryByTestId("tooltip-container")).toBeTruthy();
|
|
975
|
+
unmount();
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
it("handleOnLayout triggers getTooltipPosition with measured data", async () => {
|
|
980
|
+
const {queryByTestId, toJSON, UNSAFE_getAllByType} = renderWithTheme(
|
|
981
|
+
<Tooltip idealPosition="top" includeArrow text="Layout position test">
|
|
982
|
+
<Text>Layout trigger</Text>
|
|
983
|
+
</Tooltip>
|
|
984
|
+
);
|
|
985
|
+
|
|
986
|
+
const tree = toJSON() as TestNode;
|
|
987
|
+
const root = tree.children?.[0] as TestNode;
|
|
988
|
+
|
|
989
|
+
// Show tooltip
|
|
990
|
+
await act(async () => {
|
|
991
|
+
root.props.onTouchStart?.({nativeEvent: {}});
|
|
992
|
+
});
|
|
993
|
+
await act(async () => {
|
|
994
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
995
|
+
});
|
|
996
|
+
expect(queryByTestId("tooltip-container")).toBeTruthy();
|
|
997
|
+
|
|
998
|
+
// Find the View with onLayout and mock measure on the childrenWrapper ref
|
|
999
|
+
const {View: ViewComp} = await import("react-native");
|
|
1000
|
+
const allViews = UNSAFE_getAllByType(ViewComp);
|
|
1001
|
+
|
|
1002
|
+
// Find and mock the children wrapper ref (last View which has onPointerEnter)
|
|
1003
|
+
for (const v of allViews) {
|
|
1004
|
+
const inst = v as unknown as {measure?: Function};
|
|
1005
|
+
if (!inst.measure) {
|
|
1006
|
+
inst.measure = (
|
|
1007
|
+
cb: (
|
|
1008
|
+
x: number,
|
|
1009
|
+
y: number,
|
|
1010
|
+
width: number,
|
|
1011
|
+
height: number,
|
|
1012
|
+
pageX: number,
|
|
1013
|
+
pageY: number
|
|
1014
|
+
) => void
|
|
1015
|
+
) => {
|
|
1016
|
+
cb(0, 0, 100, 30, 200, 200);
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// Trigger onLayout on views that have it
|
|
1022
|
+
for (const v of allViews) {
|
|
1023
|
+
const props = v.props as TestNode["props"];
|
|
1024
|
+
if (props.onLayout) {
|
|
1025
|
+
await act(async () => {
|
|
1026
|
+
props.onLayout?.({
|
|
1027
|
+
nativeEvent: {layout: {height: 40, width: 200, x: 0, y: 0}},
|
|
1028
|
+
});
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
it("handleOnLayout covers different ideal positions", async () => {
|
|
1035
|
+
const positions: Array<"top" | "bottom" | "left" | "right"> = ["bottom", "left", "right"];
|
|
1036
|
+
|
|
1037
|
+
for (const position of positions) {
|
|
1038
|
+
const {queryByTestId, toJSON, UNSAFE_getAllByType, unmount} = renderWithTheme(
|
|
1039
|
+
<Tooltip idealPosition={position} includeArrow text={`Layout ${position}`}>
|
|
1040
|
+
<Text>Trigger</Text>
|
|
1041
|
+
</Tooltip>
|
|
1042
|
+
);
|
|
1043
|
+
|
|
1044
|
+
const tree = toJSON() as TestNode;
|
|
1045
|
+
const root = tree.children?.[0] as TestNode;
|
|
1046
|
+
|
|
1047
|
+
await act(async () => {
|
|
1048
|
+
root.props.onTouchStart?.({nativeEvent: {}});
|
|
1049
|
+
});
|
|
1050
|
+
await act(async () => {
|
|
1051
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
1052
|
+
});
|
|
1053
|
+
expect(queryByTestId("tooltip-container")).toBeTruthy();
|
|
1054
|
+
|
|
1055
|
+
const {View: ViewComp} = await import("react-native");
|
|
1056
|
+
const allViews = UNSAFE_getAllByType(ViewComp);
|
|
1057
|
+
for (const v of allViews) {
|
|
1058
|
+
const inst = v as unknown as {measure?: Function};
|
|
1059
|
+
if (!inst.measure) {
|
|
1060
|
+
inst.measure = (
|
|
1061
|
+
cb: (
|
|
1062
|
+
x: number,
|
|
1063
|
+
y: number,
|
|
1064
|
+
width: number,
|
|
1065
|
+
height: number,
|
|
1066
|
+
pageX: number,
|
|
1067
|
+
pageY: number
|
|
1068
|
+
) => void
|
|
1069
|
+
) => {
|
|
1070
|
+
cb(0, 0, 100, 30, 200, 200);
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
for (const v of allViews) {
|
|
1076
|
+
const props = v.props as TestNode["props"];
|
|
1077
|
+
if (props.onLayout) {
|
|
1078
|
+
await act(async () => {
|
|
1079
|
+
props.onLayout?.({
|
|
1080
|
+
nativeEvent: {layout: {height: 40, width: 200, x: 0, y: 0}},
|
|
1081
|
+
});
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
unmount();
|
|
1087
|
+
}
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
it("handleOnLayout with overflow positions triggers fallback", async () => {
|
|
1091
|
+
const {Dimensions} = await import("react-native");
|
|
1092
|
+
const originalGet = Dimensions.get;
|
|
1093
|
+
// Mock small window to force overflow
|
|
1094
|
+
Dimensions.get = () => ({fontScale: 1, height: 50, scale: 1, width: 50});
|
|
1095
|
+
|
|
1096
|
+
const {toJSON, UNSAFE_getAllByType, unmount} = renderWithTheme(
|
|
1097
|
+
<Tooltip idealPosition="top" includeArrow text="Overflow test">
|
|
1098
|
+
<Text>Overflow</Text>
|
|
1099
|
+
</Tooltip>
|
|
1100
|
+
);
|
|
1101
|
+
|
|
1102
|
+
const tree = toJSON() as TestNode;
|
|
1103
|
+
const root = tree.children?.[0] as TestNode;
|
|
1104
|
+
|
|
1105
|
+
await act(async () => {
|
|
1106
|
+
root.props.onTouchStart?.({nativeEvent: {}});
|
|
1107
|
+
});
|
|
1108
|
+
await act(async () => {
|
|
1109
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
1110
|
+
});
|
|
1111
|
+
|
|
1112
|
+
const {View: ViewComp} = await import("react-native");
|
|
1113
|
+
const allViews = UNSAFE_getAllByType(ViewComp);
|
|
1114
|
+
for (const v of allViews) {
|
|
1115
|
+
const inst = v as unknown as {measure?: Function};
|
|
1116
|
+
if (!inst.measure) {
|
|
1117
|
+
inst.measure = (
|
|
1118
|
+
cb: (
|
|
1119
|
+
x: number,
|
|
1120
|
+
y: number,
|
|
1121
|
+
width: number,
|
|
1122
|
+
height: number,
|
|
1123
|
+
pageX: number,
|
|
1124
|
+
pageY: number
|
|
1125
|
+
) => void
|
|
1126
|
+
) => {
|
|
1127
|
+
cb(0, 0, 100, 30, 0, 0);
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
for (const v of allViews) {
|
|
1133
|
+
const props = v.props as TestNode["props"];
|
|
1134
|
+
if (props.onLayout) {
|
|
1135
|
+
await act(async () => {
|
|
1136
|
+
props.onLayout?.({
|
|
1137
|
+
nativeEvent: {layout: {height: 40, width: 200, x: 0, y: 0}},
|
|
1138
|
+
});
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
Dimensions.get = originalGet;
|
|
1144
|
+
unmount();
|
|
1145
|
+
});
|
|
1146
|
+
|
|
1147
|
+
it("handleHoverIn clears existing hide timer", async () => {
|
|
1148
|
+
const {queryByTestId, toJSON} = renderWithTheme(
|
|
1149
|
+
<Tooltip text="Timer test">
|
|
1150
|
+
<Text>Hover timer</Text>
|
|
1151
|
+
</Tooltip>
|
|
1152
|
+
);
|
|
1153
|
+
|
|
1154
|
+
const tree = toJSON() as TestNode;
|
|
1155
|
+
const root = tree.children?.[0] as TestNode;
|
|
1156
|
+
|
|
1157
|
+
// Hover in then out quickly then in again to exercise timer clearing
|
|
1158
|
+
await act(async () => {
|
|
1159
|
+
root.props.onPointerEnter?.();
|
|
1160
|
+
});
|
|
1161
|
+
await act(async () => {
|
|
1162
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1165
|
+
const treeAfter = toJSON() as TestNode;
|
|
1166
|
+
const wrapper = (treeAfter.children as TestNode[])[
|
|
1167
|
+
(treeAfter.children as TestNode[]).length - 1
|
|
1168
|
+
] as TestNode;
|
|
1169
|
+
await act(async () => {
|
|
1170
|
+
wrapper.props.onPointerLeave?.();
|
|
1171
|
+
});
|
|
1172
|
+
await act(async () => {
|
|
1173
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
// Hover in again
|
|
1177
|
+
const treeAfter2 = toJSON() as TestNode;
|
|
1178
|
+
const wrapper2 = (treeAfter2.children as TestNode[])[
|
|
1179
|
+
(treeAfter2.children as TestNode[]).length - 1
|
|
1180
|
+
] as TestNode;
|
|
1181
|
+
await act(async () => {
|
|
1182
|
+
wrapper2.props.onPointerEnter?.();
|
|
1183
|
+
});
|
|
1184
|
+
await act(async () => {
|
|
1185
|
+
await new Promise((resolve) => setTimeout(resolve, 900));
|
|
1186
|
+
});
|
|
1187
|
+
expect(queryByTestId("tooltip-container")).toBeTruthy();
|
|
1188
|
+
});
|
|
1189
|
+
|
|
1190
|
+
it("handleTouchStart clears existing hide timer", async () => {
|
|
1191
|
+
const {queryByTestId, toJSON} = renderWithTheme(
|
|
1192
|
+
<Tooltip text="Touch timer">
|
|
1193
|
+
<Text>Touch timer</Text>
|
|
1194
|
+
</Tooltip>
|
|
1195
|
+
);
|
|
1196
|
+
|
|
1197
|
+
const tree = toJSON() as TestNode;
|
|
1198
|
+
const root = tree.children?.[0] as TestNode;
|
|
1199
|
+
|
|
1200
|
+
// Start showing, then hover out to set hide timer, then touch
|
|
1201
|
+
await act(async () => {
|
|
1202
|
+
root.props.onPointerEnter?.();
|
|
1203
|
+
});
|
|
1204
|
+
await act(async () => {
|
|
1205
|
+
await new Promise((resolve) => setTimeout(resolve, 900));
|
|
1206
|
+
});
|
|
1207
|
+
expect(queryByTestId("tooltip-container")).toBeTruthy();
|
|
1208
|
+
|
|
1209
|
+
const treeAfter = toJSON() as TestNode;
|
|
1210
|
+
const wrapper = (treeAfter.children as TestNode[])[
|
|
1211
|
+
(treeAfter.children as TestNode[]).length - 1
|
|
1212
|
+
] as TestNode;
|
|
1213
|
+
await act(async () => {
|
|
1214
|
+
wrapper.props.onPointerLeave?.();
|
|
1215
|
+
});
|
|
1216
|
+
|
|
1217
|
+
// Quickly touch before hide completes
|
|
1218
|
+
const treeAfter2 = toJSON() as TestNode;
|
|
1219
|
+
const wrapper2 = (treeAfter2.children as TestNode[])[
|
|
1220
|
+
(treeAfter2.children as TestNode[]).length - 1
|
|
1221
|
+
] as TestNode;
|
|
1222
|
+
await act(async () => {
|
|
1223
|
+
wrapper2.props.onTouchStart?.({nativeEvent: {}});
|
|
1224
|
+
});
|
|
1225
|
+
});
|
|
1226
|
+
|
|
1227
|
+
describe("with web platform (Arrow + handleClick)", () => {
|
|
1228
|
+
const RN = require("react-native") as Record<string, unknown>;
|
|
1229
|
+
const platform = RN.Platform as {OS: string};
|
|
1230
|
+
let origOS: string;
|
|
1231
|
+
|
|
1232
|
+
beforeEach(() => {
|
|
1233
|
+
origOS = platform.OS;
|
|
1234
|
+
platform.OS = "web";
|
|
1235
|
+
});
|
|
1236
|
+
|
|
1237
|
+
afterEach(() => {
|
|
1238
|
+
platform.OS = origOS;
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
it("renders Arrow when tooltip visible with includeArrow on web", async () => {
|
|
1242
|
+
const positions: Array<"top" | "bottom" | "left" | "right"> = [
|
|
1243
|
+
"top",
|
|
1244
|
+
"bottom",
|
|
1245
|
+
"left",
|
|
1246
|
+
"right",
|
|
1247
|
+
];
|
|
1248
|
+
|
|
1249
|
+
for (const position of positions) {
|
|
1250
|
+
const {queryByTestId, toJSON, unmount} = renderWithTheme(
|
|
1251
|
+
<Tooltip idealPosition={position} includeArrow text={`Web arrow ${position}`}>
|
|
1252
|
+
<Text>Web arrow</Text>
|
|
1253
|
+
</Tooltip>
|
|
1254
|
+
);
|
|
1255
|
+
|
|
1256
|
+
const tree = toJSON() as TestNode;
|
|
1257
|
+
const root = tree.children?.[0] as TestNode;
|
|
1258
|
+
|
|
1259
|
+
await act(async () => {
|
|
1260
|
+
root.props.onTouchStart?.({nativeEvent: {}});
|
|
1261
|
+
});
|
|
1262
|
+
await act(async () => {
|
|
1263
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
expect(queryByTestId("tooltip-container")).toBeTruthy();
|
|
1267
|
+
unmount();
|
|
1268
|
+
}
|
|
1269
|
+
});
|
|
1270
|
+
|
|
1271
|
+
it("handleClick hides tooltip on web platform", async () => {
|
|
1272
|
+
const {queryByTestId, toJSON, UNSAFE_getAllByType} = renderWithTheme(
|
|
1273
|
+
<Tooltip text="Web click hide">
|
|
1274
|
+
<Text>Click web</Text>
|
|
1275
|
+
</Tooltip>
|
|
1276
|
+
);
|
|
1277
|
+
|
|
1278
|
+
const tree = toJSON() as TestNode;
|
|
1279
|
+
const root = tree.children?.[0] as TestNode;
|
|
1280
|
+
|
|
1281
|
+
await act(async () => {
|
|
1282
|
+
root.props.onTouchStart?.({nativeEvent: {}});
|
|
1283
|
+
});
|
|
1284
|
+
await act(async () => {
|
|
1285
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
1286
|
+
});
|
|
1287
|
+
expect(queryByTestId("tooltip-container")).toBeTruthy();
|
|
1288
|
+
|
|
1289
|
+
// Find View with onPress (handleClick bound on web)
|
|
1290
|
+
const ViewComp = (RN as {View: React.ComponentType}).View;
|
|
1291
|
+
const allViews = UNSAFE_getAllByType(ViewComp);
|
|
1292
|
+
const clickableView = allViews.find(
|
|
1293
|
+
(v: {props: Record<string, unknown>}) => typeof v.props.onPress === "function"
|
|
1294
|
+
);
|
|
1295
|
+
|
|
1296
|
+
if (clickableView) {
|
|
1297
|
+
await act(async () => {
|
|
1298
|
+
(clickableView.props as {onPress: () => void}).onPress();
|
|
1299
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
});
|
|
1303
|
+
|
|
1304
|
+
it("exercises getTooltipPosition via onLayout with measure mock", async () => {
|
|
1305
|
+
const {queryByTestId, toJSON, UNSAFE_getAllByType} = renderWithTheme(
|
|
1306
|
+
<Tooltip idealPosition="top" includeArrow text="Measure test">
|
|
1307
|
+
<Text>Measure</Text>
|
|
1308
|
+
</Tooltip>
|
|
1309
|
+
);
|
|
1310
|
+
|
|
1311
|
+
const tree = toJSON() as TestNode;
|
|
1312
|
+
const root = tree.children?.[0] as TestNode;
|
|
1313
|
+
|
|
1314
|
+
await act(async () => {
|
|
1315
|
+
root.props.onTouchStart?.({nativeEvent: {}});
|
|
1316
|
+
});
|
|
1317
|
+
await act(async () => {
|
|
1318
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
1319
|
+
});
|
|
1320
|
+
expect(queryByTestId("tooltip-container")).toBeTruthy();
|
|
1321
|
+
|
|
1322
|
+
// Find the wrapper View that has the ref (has onPointerEnter)
|
|
1323
|
+
const ViewComp = (RN as {View: React.ComponentType}).View;
|
|
1324
|
+
const allViews = UNSAFE_getAllByType(ViewComp);
|
|
1325
|
+
const wrapperView = allViews.find(
|
|
1326
|
+
(v: {props: Record<string, unknown>}) => typeof v.props.onPointerEnter === "function"
|
|
1327
|
+
);
|
|
1328
|
+
|
|
1329
|
+
// Access the component fiber to find and set the ref
|
|
1330
|
+
if (wrapperView) {
|
|
1331
|
+
const fiber = (wrapperView as unknown as {_fiber?: {ref?: {current: unknown}}})._fiber;
|
|
1332
|
+
if (fiber?.ref && typeof fiber.ref === "object") {
|
|
1333
|
+
(fiber.ref as {current: unknown}).current = {
|
|
1334
|
+
measure: (
|
|
1335
|
+
cb: (
|
|
1336
|
+
x: number,
|
|
1337
|
+
y: number,
|
|
1338
|
+
width: number,
|
|
1339
|
+
height: number,
|
|
1340
|
+
pageX: number,
|
|
1341
|
+
pageY: number
|
|
1342
|
+
) => void
|
|
1343
|
+
) => {
|
|
1344
|
+
cb(0, 0, 120, 40, 150, 200);
|
|
1345
|
+
},
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
// Now trigger onLayout
|
|
1351
|
+
const treeVisible = toJSON() as TestNode;
|
|
1352
|
+
const findOnLayout = (node: TestNode): TestNode["props"]["onLayout"] | undefined => {
|
|
1353
|
+
if (node.props?.onLayout) {
|
|
1354
|
+
return node.props.onLayout;
|
|
1355
|
+
}
|
|
1356
|
+
if (node.children) {
|
|
1357
|
+
for (const child of node.children) {
|
|
1358
|
+
if (typeof child !== "string") {
|
|
1359
|
+
const found = findOnLayout(child);
|
|
1360
|
+
if (found) {
|
|
1361
|
+
return found;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
return undefined;
|
|
1367
|
+
};
|
|
1368
|
+
|
|
1369
|
+
const layoutHandler = findOnLayout(treeVisible);
|
|
1370
|
+
if (layoutHandler) {
|
|
1371
|
+
await act(async () => {
|
|
1372
|
+
layoutHandler({
|
|
1373
|
+
nativeEvent: {layout: {height: 30, width: 180, x: 0, y: 0}},
|
|
1374
|
+
});
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
});
|
|
1378
|
+
|
|
1379
|
+
it("exercises getTooltipPosition for each position via fiber ref", async () => {
|
|
1380
|
+
const positions: Array<"top" | "bottom" | "left" | "right"> = ["bottom", "left", "right"];
|
|
1381
|
+
|
|
1382
|
+
for (const position of positions) {
|
|
1383
|
+
const {queryByTestId, toJSON, UNSAFE_getAllByType, unmount} = renderWithTheme(
|
|
1384
|
+
<Tooltip idealPosition={position} includeArrow text={`Measure ${position}`}>
|
|
1385
|
+
<Text>Measure pos</Text>
|
|
1386
|
+
</Tooltip>
|
|
1387
|
+
);
|
|
1388
|
+
|
|
1389
|
+
const tree = toJSON() as TestNode;
|
|
1390
|
+
const root = tree.children?.[0] as TestNode;
|
|
1391
|
+
|
|
1392
|
+
await act(async () => {
|
|
1393
|
+
root.props.onTouchStart?.({nativeEvent: {}});
|
|
1394
|
+
});
|
|
1395
|
+
await act(async () => {
|
|
1396
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
1397
|
+
});
|
|
1398
|
+
expect(queryByTestId("tooltip-container")).toBeTruthy();
|
|
1399
|
+
|
|
1400
|
+
const ViewComp = (RN as {View: React.ComponentType}).View;
|
|
1401
|
+
const allViews = UNSAFE_getAllByType(ViewComp);
|
|
1402
|
+
const wrapperView = allViews.find(
|
|
1403
|
+
(v: {props: Record<string, unknown>}) => typeof v.props.onPointerEnter === "function"
|
|
1404
|
+
);
|
|
1405
|
+
|
|
1406
|
+
if (wrapperView) {
|
|
1407
|
+
const fiber = (wrapperView as unknown as {_fiber?: {ref?: {current: unknown}}})._fiber;
|
|
1408
|
+
if (fiber?.ref && typeof fiber.ref === "object") {
|
|
1409
|
+
(fiber.ref as {current: unknown}).current = {
|
|
1410
|
+
measure: (
|
|
1411
|
+
cb: (
|
|
1412
|
+
x: number,
|
|
1413
|
+
y: number,
|
|
1414
|
+
width: number,
|
|
1415
|
+
height: number,
|
|
1416
|
+
pageX: number,
|
|
1417
|
+
pageY: number
|
|
1418
|
+
) => void
|
|
1419
|
+
) => {
|
|
1420
|
+
cb(0, 0, 120, 40, 150, 200);
|
|
1421
|
+
},
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
const treeVisible = toJSON() as TestNode;
|
|
1427
|
+
const findOnLayout = (node: TestNode): TestNode["props"]["onLayout"] | undefined => {
|
|
1428
|
+
if (node.props?.onLayout) {
|
|
1429
|
+
return node.props.onLayout;
|
|
1430
|
+
}
|
|
1431
|
+
if (node.children) {
|
|
1432
|
+
for (const child of node.children) {
|
|
1433
|
+
if (typeof child !== "string") {
|
|
1434
|
+
const found = findOnLayout(child);
|
|
1435
|
+
if (found) {
|
|
1436
|
+
return found;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
return undefined;
|
|
1442
|
+
};
|
|
1443
|
+
|
|
1444
|
+
const layoutHandler = findOnLayout(treeVisible);
|
|
1445
|
+
if (layoutHandler) {
|
|
1446
|
+
await act(async () => {
|
|
1447
|
+
layoutHandler({
|
|
1448
|
+
nativeEvent: {layout: {height: 30, width: 180, x: 0, y: 0}},
|
|
1449
|
+
});
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
unmount();
|
|
1454
|
+
}
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1457
|
+
it("exercises getTooltipPosition overflow fallbacks via fiber ref", async () => {
|
|
1458
|
+
const dims = RN.Dimensions as {get: Function};
|
|
1459
|
+
const origGet = dims.get;
|
|
1460
|
+
dims.get = () => ({fontScale: 1, height: 100, scale: 1, width: 100});
|
|
1461
|
+
|
|
1462
|
+
const {toJSON, UNSAFE_getAllByType, unmount} = renderWithTheme(
|
|
1463
|
+
<Tooltip idealPosition="top" includeArrow text="Overflow web">
|
|
1464
|
+
<Text>Overflow</Text>
|
|
1465
|
+
</Tooltip>
|
|
1466
|
+
);
|
|
1467
|
+
|
|
1468
|
+
const tree = toJSON() as TestNode;
|
|
1469
|
+
const root = tree.children?.[0] as TestNode;
|
|
1470
|
+
|
|
1471
|
+
await act(async () => {
|
|
1472
|
+
root.props.onTouchStart?.({nativeEvent: {}});
|
|
1473
|
+
});
|
|
1474
|
+
await act(async () => {
|
|
1475
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
1476
|
+
});
|
|
1477
|
+
|
|
1478
|
+
const ViewComp = (RN as {View: React.ComponentType}).View;
|
|
1479
|
+
const allViews = UNSAFE_getAllByType(ViewComp);
|
|
1480
|
+
const wrapperView = allViews.find(
|
|
1481
|
+
(v: {props: Record<string, unknown>}) => typeof v.props.onPointerEnter === "function"
|
|
1482
|
+
);
|
|
1483
|
+
|
|
1484
|
+
if (wrapperView) {
|
|
1485
|
+
const fiber = (wrapperView as unknown as {_fiber?: {ref?: {current: unknown}}})._fiber;
|
|
1486
|
+
if (fiber?.ref && typeof fiber.ref === "object") {
|
|
1487
|
+
(fiber.ref as {current: unknown}).current = {
|
|
1488
|
+
measure: (
|
|
1489
|
+
cb: (
|
|
1490
|
+
x: number,
|
|
1491
|
+
y: number,
|
|
1492
|
+
width: number,
|
|
1493
|
+
height: number,
|
|
1494
|
+
pageX: number,
|
|
1495
|
+
pageY: number
|
|
1496
|
+
) => void
|
|
1497
|
+
) => {
|
|
1498
|
+
cb(0, 0, 200, 50, 0, 0);
|
|
1499
|
+
},
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
const treeVisible = toJSON() as TestNode;
|
|
1505
|
+
const findOnLayout = (node: TestNode): TestNode["props"]["onLayout"] | undefined => {
|
|
1506
|
+
if (node.props?.onLayout) {
|
|
1507
|
+
return node.props.onLayout;
|
|
1508
|
+
}
|
|
1509
|
+
if (node.children) {
|
|
1510
|
+
for (const child of node.children) {
|
|
1511
|
+
if (typeof child !== "string") {
|
|
1512
|
+
const found = findOnLayout(child);
|
|
1513
|
+
if (found) {
|
|
1514
|
+
return found;
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
return undefined;
|
|
1520
|
+
};
|
|
1521
|
+
|
|
1522
|
+
const layoutHandler = findOnLayout(treeVisible);
|
|
1523
|
+
if (layoutHandler) {
|
|
1524
|
+
await act(async () => {
|
|
1525
|
+
layoutHandler({
|
|
1526
|
+
nativeEvent: {layout: {height: 200, width: 300, x: 0, y: 0}},
|
|
1527
|
+
});
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
dims.get = origGet;
|
|
1532
|
+
unmount();
|
|
1533
|
+
});
|
|
1534
|
+
|
|
1535
|
+
it("exercises ref error path when ref has no measure", async () => {
|
|
1536
|
+
const {queryByTestId, toJSON, UNSAFE_getAllByType} = renderWithTheme(
|
|
1537
|
+
<Tooltip text="Error ref">
|
|
1538
|
+
<Text>Error</Text>
|
|
1539
|
+
</Tooltip>
|
|
1540
|
+
);
|
|
1541
|
+
|
|
1542
|
+
const tree = toJSON() as TestNode;
|
|
1543
|
+
const root = tree.children?.[0] as TestNode;
|
|
1544
|
+
|
|
1545
|
+
await act(async () => {
|
|
1546
|
+
root.props.onTouchStart?.({nativeEvent: {}});
|
|
1547
|
+
});
|
|
1548
|
+
await act(async () => {
|
|
1549
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
1550
|
+
});
|
|
1551
|
+
expect(queryByTestId("tooltip-container")).toBeTruthy();
|
|
1552
|
+
|
|
1553
|
+
// Set ref to object without measure
|
|
1554
|
+
const ViewComp = (RN as {View: React.ComponentType}).View;
|
|
1555
|
+
const allViews = UNSAFE_getAllByType(ViewComp);
|
|
1556
|
+
const wrapperView = allViews.find(
|
|
1557
|
+
(v: {props: Record<string, unknown>}) => typeof v.props.onPointerEnter === "function"
|
|
1558
|
+
);
|
|
1559
|
+
|
|
1560
|
+
if (wrapperView) {
|
|
1561
|
+
const fiber = (wrapperView as unknown as {_fiber?: {ref?: {current: unknown}}})._fiber;
|
|
1562
|
+
if (fiber?.ref && typeof fiber.ref === "object") {
|
|
1563
|
+
(fiber.ref as {current: unknown}).current = {};
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
const treeVisible = toJSON() as TestNode;
|
|
1568
|
+
const findOnLayout = (node: TestNode): TestNode["props"]["onLayout"] | undefined => {
|
|
1569
|
+
if (node.props?.onLayout) {
|
|
1570
|
+
return node.props.onLayout;
|
|
1571
|
+
}
|
|
1572
|
+
if (node.children) {
|
|
1573
|
+
for (const child of node.children) {
|
|
1574
|
+
if (typeof child !== "string") {
|
|
1575
|
+
const found = findOnLayout(child);
|
|
1576
|
+
if (found) {
|
|
1577
|
+
return found;
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
return undefined;
|
|
1583
|
+
};
|
|
1584
|
+
|
|
1585
|
+
const layoutHandler = findOnLayout(treeVisible);
|
|
1586
|
+
if (layoutHandler) {
|
|
1587
|
+
await act(async () => {
|
|
1588
|
+
layoutHandler({
|
|
1589
|
+
nativeEvent: {layout: {height: 30, width: 180, x: 0, y: 0}},
|
|
1590
|
+
});
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
});
|
|
1594
|
+
});
|
|
304
1595
|
});
|