@khanacademy/wonder-blocks-tooltip 2.4.1 → 2.4.3
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/CHANGELOG.md +18 -0
- package/package.json +6 -6
- package/src/components/__tests__/tooltip-anchor.test.tsx +0 -1003
- package/src/components/__tests__/tooltip-bubble.test.tsx +0 -64
- package/src/components/__tests__/tooltip-popper.test.tsx +0 -72
- package/src/components/__tests__/tooltip-tail.test.tsx +0 -135
- package/src/components/__tests__/tooltip.integration.test.tsx +0 -116
- package/src/components/__tests__/tooltip.test.tsx +0 -379
- package/src/components/tooltip-anchor.tsx +0 -341
- package/src/components/tooltip-bubble.tsx +0 -132
- package/src/components/tooltip-content.tsx +0 -96
- package/src/components/tooltip-popper.tsx +0 -265
- package/src/components/tooltip-tail.tsx +0 -445
- package/src/components/tooltip.tsx +0 -306
- package/src/index.ts +0 -10
- package/src/util/__tests__/__snapshots__/active-tracker.test.ts.snap +0 -3
- package/src/util/__tests__/__snapshots__/ref-tracker.test.tsx.snap +0 -3
- package/src/util/__tests__/active-tracker.test.ts +0 -142
- package/src/util/__tests__/ref-tracker.test.tsx +0 -158
- package/src/util/active-tracker.ts +0 -92
- package/src/util/constants.ts +0 -6
- package/src/util/ref-tracker.ts +0 -48
- package/src/util/types.ts +0 -46
- package/tsconfig-build.json +0 -15
- package/tsconfig-build.tsbuildinfo +0 -1
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import {render, screen} from "@testing-library/react";
|
|
3
|
-
|
|
4
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
5
|
-
|
|
6
|
-
import TooltipBubble from "../tooltip-bubble";
|
|
7
|
-
import TooltipContent from "../tooltip-content";
|
|
8
|
-
|
|
9
|
-
describe("TooltipBubble", () => {
|
|
10
|
-
// A little helper method to make the actual test more readable.
|
|
11
|
-
const makePopperProps = () =>
|
|
12
|
-
({
|
|
13
|
-
placement: "top",
|
|
14
|
-
tailOffset: {
|
|
15
|
-
top: "0",
|
|
16
|
-
left: "50",
|
|
17
|
-
bottom: undefined,
|
|
18
|
-
right: undefined,
|
|
19
|
-
transform: "translate3d(50, 0, 0)",
|
|
20
|
-
},
|
|
21
|
-
} as const);
|
|
22
|
-
|
|
23
|
-
test("updates reference to bubble container", async () => {
|
|
24
|
-
// Arrange
|
|
25
|
-
// Get some props and set the ref to our assert, that way we assert
|
|
26
|
-
// when the bubble component is mounted.
|
|
27
|
-
const props = makePopperProps();
|
|
28
|
-
|
|
29
|
-
// Do some casting to pretend this is `TooltipContent`. That way we are
|
|
30
|
-
// isolating behaviors a bit more.
|
|
31
|
-
const fakeContent = (
|
|
32
|
-
<View id="content">Some content</View>
|
|
33
|
-
) as React.ReactElement<React.ComponentProps<typeof TooltipContent>>;
|
|
34
|
-
|
|
35
|
-
// Act
|
|
36
|
-
render(
|
|
37
|
-
<View>
|
|
38
|
-
<TooltipBubble
|
|
39
|
-
id="bubble"
|
|
40
|
-
placement={props.placement}
|
|
41
|
-
tailOffset={props.tailOffset}
|
|
42
|
-
updateBubbleRef={jest.fn()}
|
|
43
|
-
onActiveChanged={() => {}}
|
|
44
|
-
>
|
|
45
|
-
{fakeContent}
|
|
46
|
-
</TooltipBubble>
|
|
47
|
-
</View>,
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
// Is the node a mounted element?
|
|
51
|
-
const tooltip = await screen.findByRole("tooltip");
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* All we're doing is making sure we got called and verifying that we
|
|
55
|
-
* got called with an element we expect.
|
|
56
|
-
*/
|
|
57
|
-
// Assert
|
|
58
|
-
// Did we apply our data attribute?
|
|
59
|
-
expect(tooltip.getAttribute("data-placement")).toBe("top");
|
|
60
|
-
|
|
61
|
-
// Did we render our content?
|
|
62
|
-
expect(screen.getByText("Some content")).toBeInTheDocument();
|
|
63
|
-
});
|
|
64
|
-
});
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import * as ReactDOM from "react-dom";
|
|
3
|
-
import {render} from "@testing-library/react";
|
|
4
|
-
|
|
5
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
6
|
-
|
|
7
|
-
import TooltipBubble from "../tooltip-bubble";
|
|
8
|
-
import TooltipPopper from "../tooltip-popper";
|
|
9
|
-
|
|
10
|
-
type State = {
|
|
11
|
-
ref?: HTMLElement;
|
|
12
|
-
};
|
|
13
|
-
/**
|
|
14
|
-
* A little wrapper for the TooltipPopper so that we can provide an anchor
|
|
15
|
-
* element reference and test that the children get rendered.
|
|
16
|
-
*/
|
|
17
|
-
class TestHarness extends React.Component<any, State> {
|
|
18
|
-
state: State = {};
|
|
19
|
-
|
|
20
|
-
updateRef(ref: any) {
|
|
21
|
-
const actualRef = ref && ReactDOM.findDOMNode(ref);
|
|
22
|
-
if (actualRef && this.state.ref !== actualRef) {
|
|
23
|
-
this.setState({ref: actualRef as HTMLElement});
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
render(): React.ReactElement {
|
|
28
|
-
const fakeBubble = (
|
|
29
|
-
<View ref={(ref: any) => this.props.resultRef(ref)}>
|
|
30
|
-
Fake bubble
|
|
31
|
-
</View>
|
|
32
|
-
) as React.ReactElement<React.ComponentProps<typeof TooltipBubble>>;
|
|
33
|
-
return (
|
|
34
|
-
<View>
|
|
35
|
-
<View ref={(ref: any) => this.updateRef(ref)}>Anchor</View>
|
|
36
|
-
<TooltipPopper
|
|
37
|
-
placement={this.props.placement}
|
|
38
|
-
anchorElement={this.state.ref}
|
|
39
|
-
>
|
|
40
|
-
{(props: any) => fakeBubble}
|
|
41
|
-
</TooltipPopper>
|
|
42
|
-
</View>
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
describe("TooltipPopper", () => {
|
|
48
|
-
// The TooltipPopper component is just a wrapper around react-popper.
|
|
49
|
-
// PopperJS requires full visual rendering and we don't do that here as
|
|
50
|
-
// we're not in a browser.
|
|
51
|
-
// So, let's do a test that we at least render the content how we expect
|
|
52
|
-
// and use other things to test the overall placement things.
|
|
53
|
-
test("ensure component renders", async () => {
|
|
54
|
-
// Arrange
|
|
55
|
-
const ref = await new Promise((resolve: any, reject: any) => {
|
|
56
|
-
const nodes = (
|
|
57
|
-
<View>
|
|
58
|
-
<TestHarness placement="bottom" resultRef={resolve} />
|
|
59
|
-
</View>
|
|
60
|
-
);
|
|
61
|
-
render(nodes);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
if (!ref) {
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Act
|
|
69
|
-
// Assert
|
|
70
|
-
expect(ref).toBeDefined();
|
|
71
|
-
});
|
|
72
|
-
});
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import {render} from "@testing-library/react";
|
|
3
|
-
|
|
4
|
-
import TooltipTail from "../tooltip-tail";
|
|
5
|
-
|
|
6
|
-
import type {Placement} from "../../util/types";
|
|
7
|
-
|
|
8
|
-
describe("TooltipTail", () => {
|
|
9
|
-
describe("#render", () => {
|
|
10
|
-
test("unknown placement, throws", () => {
|
|
11
|
-
// Arrange
|
|
12
|
-
const fakePlacement = "notaplacement" as Placement;
|
|
13
|
-
const nodes = <TooltipTail placement={fakePlacement} />;
|
|
14
|
-
|
|
15
|
-
// Act
|
|
16
|
-
const underTest = () => render(nodes);
|
|
17
|
-
|
|
18
|
-
// Assert
|
|
19
|
-
expect(underTest).toThrowErrorMatchingInlineSnapshot(
|
|
20
|
-
`"Unknown placement: notaplacement"`,
|
|
21
|
-
);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test("known placement, does not throw", () => {
|
|
25
|
-
// Arrange
|
|
26
|
-
const testPoints = ["top", "right", "bottom", "left"];
|
|
27
|
-
const makeNode = (p: any) => <TooltipTail placement={p} />;
|
|
28
|
-
|
|
29
|
-
// Act
|
|
30
|
-
const testees = testPoints.map(
|
|
31
|
-
(tp: any) => () => render(makeNode(tp)),
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
// Assert
|
|
35
|
-
for (const testee of testees) {
|
|
36
|
-
expect(testee).not.toThrowError();
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("should render a visible tail", () => {
|
|
41
|
-
// Arrange
|
|
42
|
-
const nodes = <TooltipTail placement="top" />;
|
|
43
|
-
|
|
44
|
-
// Act
|
|
45
|
-
const {container} = render(nodes);
|
|
46
|
-
|
|
47
|
-
// Assert
|
|
48
|
-
expect(container).toMatchInlineSnapshot(`
|
|
49
|
-
<div>
|
|
50
|
-
<div
|
|
51
|
-
class=""
|
|
52
|
-
data-placement="top"
|
|
53
|
-
style="align-items: stretch; border-width: 0px; border-style: solid; box-sizing: border-box; display: flex; flex-direction: column; margin: 0px; padding: 0px; position: relative; z-index: 0; min-height: 0; min-width: 0; pointer-events: none; top: -1px; width: 40px; height: 20px;"
|
|
54
|
-
>
|
|
55
|
-
<svg
|
|
56
|
-
aria-hidden="true"
|
|
57
|
-
class="arrow_oo4scr"
|
|
58
|
-
height="12"
|
|
59
|
-
style="margin-left: 8px; margin-right: 8px; padding-bottom: 8px;"
|
|
60
|
-
width="24"
|
|
61
|
-
>
|
|
62
|
-
<filter
|
|
63
|
-
height="200%"
|
|
64
|
-
id="tooltip-dropshadow-top-3"
|
|
65
|
-
width="200%"
|
|
66
|
-
x="-50%"
|
|
67
|
-
y="-50%"
|
|
68
|
-
>
|
|
69
|
-
<fegaussianblur
|
|
70
|
-
in="SourceAlpha"
|
|
71
|
-
stdDeviation="3"
|
|
72
|
-
/>
|
|
73
|
-
<fecomponenttransfer>
|
|
74
|
-
<fefunca
|
|
75
|
-
slope="0.3"
|
|
76
|
-
type="linear"
|
|
77
|
-
/>
|
|
78
|
-
</fecomponenttransfer>
|
|
79
|
-
</filter>
|
|
80
|
-
<g
|
|
81
|
-
transform="translate(0,5.5)"
|
|
82
|
-
>
|
|
83
|
-
<polyline
|
|
84
|
-
fill="rgba(33,36,44,0.16)"
|
|
85
|
-
filter="url(#tooltip-dropshadow-top-3)"
|
|
86
|
-
points="0,0 12,12 24,0"
|
|
87
|
-
stroke="rgba(33,36,44,0.32)"
|
|
88
|
-
/>
|
|
89
|
-
</g>
|
|
90
|
-
<polyline
|
|
91
|
-
fill="#ffffff"
|
|
92
|
-
points="0,0 12,12 24,0"
|
|
93
|
-
stroke="#ffffff"
|
|
94
|
-
/>
|
|
95
|
-
<polyline
|
|
96
|
-
fill="#ffffff"
|
|
97
|
-
points="0,0 12,12 24,0"
|
|
98
|
-
stroke="rgba(33,36,44,0.16)"
|
|
99
|
-
/>
|
|
100
|
-
<polyline
|
|
101
|
-
points="0,-0.5 24,-0.5"
|
|
102
|
-
stroke="#ffffff"
|
|
103
|
-
/>
|
|
104
|
-
</svg>
|
|
105
|
-
</div>
|
|
106
|
-
</div>
|
|
107
|
-
`);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("should render a spacer when show is false", () => {
|
|
111
|
-
// Arrange
|
|
112
|
-
const nodes = <TooltipTail placement="top" show={false} />;
|
|
113
|
-
|
|
114
|
-
// Act
|
|
115
|
-
const {container} = render(nodes);
|
|
116
|
-
|
|
117
|
-
// Assert
|
|
118
|
-
expect(container).toMatchInlineSnapshot(`
|
|
119
|
-
<div>
|
|
120
|
-
<div
|
|
121
|
-
class=""
|
|
122
|
-
data-placement="top"
|
|
123
|
-
style="align-items: stretch; border-width: 0px; border-style: solid; box-sizing: border-box; display: flex; flex-direction: column; margin: 0px; padding: 0px; position: relative; z-index: 0; min-height: 0; min-width: 0; pointer-events: none; top: -1px; width: 40px; height: 20px;"
|
|
124
|
-
>
|
|
125
|
-
<div
|
|
126
|
-
aria-hidden="true"
|
|
127
|
-
class=""
|
|
128
|
-
style="align-items: stretch; border-width: 0px; border-style: solid; box-sizing: border-box; display: flex; flex-direction: column; margin: 0px; padding: 0px; position: relative; z-index: 0; min-height: 0; min-width: 0; width: 12px; flex-basis: 12px; flex-shrink: 0;"
|
|
129
|
-
/>
|
|
130
|
-
</div>
|
|
131
|
-
</div>
|
|
132
|
-
`);
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
});
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
import {render, screen, fireEvent} from "@testing-library/react";
|
|
4
|
-
import {userEvent} from "@testing-library/user-event";
|
|
5
|
-
|
|
6
|
-
import Tooltip from "../tooltip";
|
|
7
|
-
|
|
8
|
-
describe("tooltip integration tests", () => {
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
jest.useFakeTimers();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it("should set timeoutId be null when TooltipBubble is active", async () => {
|
|
14
|
-
// Arrange
|
|
15
|
-
const ue = userEvent.setup({
|
|
16
|
-
advanceTimers: jest.advanceTimersByTime,
|
|
17
|
-
});
|
|
18
|
-
render(<Tooltip content="hello, world">an anchor</Tooltip>);
|
|
19
|
-
const anchor = await screen.findByText("an anchor");
|
|
20
|
-
|
|
21
|
-
// Act
|
|
22
|
-
await ue.hover(anchor);
|
|
23
|
-
// There's a 100ms delay before TooltipAnchor calls _setActiveState with
|
|
24
|
-
// instant set to true. This second call is what actually triggers the
|
|
25
|
-
// call to this.props.onActiveChanged() which updates Tooltip's active
|
|
26
|
-
// state.
|
|
27
|
-
jest.runAllTimers();
|
|
28
|
-
|
|
29
|
-
// Assert
|
|
30
|
-
expect(await screen.findByRole("tooltip")).toBeInTheDocument();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("should hide the bubble on mouseleave on TooltipAnchor", async () => {
|
|
34
|
-
// Arrange
|
|
35
|
-
const ue = userEvent.setup({
|
|
36
|
-
advanceTimers: jest.advanceTimersByTime,
|
|
37
|
-
});
|
|
38
|
-
render(<Tooltip content="hello, world">an anchor</Tooltip>);
|
|
39
|
-
|
|
40
|
-
const anchor = await screen.findByText("an anchor");
|
|
41
|
-
await ue.hover(anchor);
|
|
42
|
-
|
|
43
|
-
// Act
|
|
44
|
-
await ue.unhover(anchor);
|
|
45
|
-
// There's a 100ms delay before TooltipAnchor calls _setActiveState with
|
|
46
|
-
// instant set to true. This second call is what actually triggers the
|
|
47
|
-
// call to this.props.onActiveChanged() which updates Tooltip's active
|
|
48
|
-
// state.
|
|
49
|
-
jest.runAllTimers();
|
|
50
|
-
|
|
51
|
-
// Assert
|
|
52
|
-
expect(screen.queryByRole("tooltip")).toBeNull();
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("should close TooltipBubble on mouseleave on TooltipBubble", async () => {
|
|
56
|
-
// Arrange
|
|
57
|
-
const ue = userEvent.setup({
|
|
58
|
-
advanceTimers: jest.advanceTimersByTime,
|
|
59
|
-
});
|
|
60
|
-
render(<Tooltip content="hello, world">an anchor</Tooltip>);
|
|
61
|
-
|
|
62
|
-
const anchor = await screen.findByText("an anchor");
|
|
63
|
-
await ue.hover(anchor);
|
|
64
|
-
// hover on bubble to keep it active
|
|
65
|
-
// Need to run the timers or we won't get the bubble wrapper to show.
|
|
66
|
-
jest.runAllTimers();
|
|
67
|
-
const bubbleWrapper = await screen.findByRole("tooltip");
|
|
68
|
-
await ue.unhover(anchor);
|
|
69
|
-
|
|
70
|
-
// Used because RTL complains about the bubble containing a child
|
|
71
|
-
// element with pointerEvents: none
|
|
72
|
-
// eslint-disable-next-line testing-library/prefer-user-event
|
|
73
|
-
fireEvent.mouseEnter(bubbleWrapper);
|
|
74
|
-
|
|
75
|
-
// Act
|
|
76
|
-
// eslint-disable-next-line testing-library/prefer-user-event
|
|
77
|
-
fireEvent.mouseLeave(bubbleWrapper);
|
|
78
|
-
// There's a 100ms delay before TooltipAnchor calls _setActiveState with
|
|
79
|
-
// instant set to true. This second call is what actually triggers the
|
|
80
|
-
// call to this.props.onActiveChanged() which updates Tooltip's active
|
|
81
|
-
// state.
|
|
82
|
-
jest.runAllTimers();
|
|
83
|
-
|
|
84
|
-
// Assert
|
|
85
|
-
expect(screen.queryByRole("tooltip")).not.toBeInTheDocument();
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it("should have an opened tooltip when subsequent mouseenter, mouseleave, and mouseenter events occur", async () => {
|
|
89
|
-
// This a test case that simulates a bug in Firefox where a tooltip will
|
|
90
|
-
// sometimes flicker and not stay opened due to the browser triggering
|
|
91
|
-
// subsequent mouseenter, mouseleave, and mouseenter events
|
|
92
|
-
|
|
93
|
-
// Arrange
|
|
94
|
-
const ue = userEvent.setup({
|
|
95
|
-
advanceTimers: jest.advanceTimersByTime,
|
|
96
|
-
});
|
|
97
|
-
render(<Tooltip content="hello, world">an anchor</Tooltip>);
|
|
98
|
-
|
|
99
|
-
// Act
|
|
100
|
-
const anchor = await screen.findByText("an anchor");
|
|
101
|
-
// Trigger initial mouseenter event on anchor and let the timeout complete
|
|
102
|
-
// to activate the tooltip
|
|
103
|
-
await ue.hover(anchor);
|
|
104
|
-
await jest.runAllTimers();
|
|
105
|
-
expect(screen.getByRole("tooltip")).toBeInTheDocument();
|
|
106
|
-
// Trigger mouseleave and mouseenter event and run timers only after
|
|
107
|
-
// both have been triggered. This simulates the mouseenter event being
|
|
108
|
-
// triggered before the tooltip is closed from the mouseleave event
|
|
109
|
-
await ue.unhover(anchor);
|
|
110
|
-
await ue.hover(anchor);
|
|
111
|
-
await jest.runAllTimers();
|
|
112
|
-
|
|
113
|
-
// Assert
|
|
114
|
-
expect(screen.getByRole("tooltip")).toBeInTheDocument();
|
|
115
|
-
});
|
|
116
|
-
});
|