@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,306 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The Tooltip component provides the means to anchor some additional
|
|
3
|
-
* information to some content. The additional information is shown in a
|
|
4
|
-
* callout that hovers above the page content. This additional information is
|
|
5
|
-
* invoked by hovering over the anchored content, or focusing all or part of the
|
|
6
|
-
* anchored content.
|
|
7
|
-
*
|
|
8
|
-
* This component is structured as follows:
|
|
9
|
-
*
|
|
10
|
-
* Tooltip (this component)
|
|
11
|
-
* - TooltipAnchor (provides hover/focus behaviors on anchored content)
|
|
12
|
-
* - TooltipPortalMounter (creates portal into which the callout is rendered)
|
|
13
|
-
* --------------------------- [PORTAL BOUNDARY] ------------------------------
|
|
14
|
-
* - TooltipPopper (provides positioning for the callout using react-popper)
|
|
15
|
-
* - TooltipBubble (renders the callout borders, background and shadow)
|
|
16
|
-
* - TooltipContent (renders the callout content; the actual information)
|
|
17
|
-
* - TooltipTail (renders the callout tail and shadow that points from the
|
|
18
|
-
* callout to the anchor content)
|
|
19
|
-
*/
|
|
20
|
-
import * as React from "react";
|
|
21
|
-
import * as ReactDOM from "react-dom";
|
|
22
|
-
|
|
23
|
-
import {
|
|
24
|
-
UniqueIDProvider,
|
|
25
|
-
IIdentifierFactory,
|
|
26
|
-
} from "@khanacademy/wonder-blocks-core";
|
|
27
|
-
import {maybeGetPortalMountedModalHostElement} from "@khanacademy/wonder-blocks-modal";
|
|
28
|
-
import type {Typography} from "@khanacademy/wonder-blocks-typography";
|
|
29
|
-
import type {AriaProps} from "@khanacademy/wonder-blocks-core";
|
|
30
|
-
import {color} from "@khanacademy/wonder-blocks-tokens";
|
|
31
|
-
|
|
32
|
-
import TooltipAnchor from "./tooltip-anchor";
|
|
33
|
-
import TooltipBubble from "./tooltip-bubble";
|
|
34
|
-
import TooltipContent from "./tooltip-content";
|
|
35
|
-
import TooltipPopper from "./tooltip-popper";
|
|
36
|
-
import type {ContentStyle, Placement} from "../util/types";
|
|
37
|
-
|
|
38
|
-
type Props = AriaProps &
|
|
39
|
-
Readonly<{
|
|
40
|
-
/**
|
|
41
|
-
* The content for anchoring the tooltip.
|
|
42
|
-
* This component will be used to position the tooltip.
|
|
43
|
-
*/
|
|
44
|
-
children: React.ReactElement<any> | string;
|
|
45
|
-
/**
|
|
46
|
-
* Optional title for the tooltip content.
|
|
47
|
-
*/
|
|
48
|
-
title?: string | React.ReactElement<React.ComponentProps<Typography>>;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Whether the tooltip should update its position when the anchor
|
|
52
|
-
* element changes size or position. Defaults to false.
|
|
53
|
-
*/
|
|
54
|
-
autoUpdate?: boolean;
|
|
55
|
-
/**
|
|
56
|
-
* The content to render in the tooltip.
|
|
57
|
-
*/
|
|
58
|
-
content:
|
|
59
|
-
| string
|
|
60
|
-
| React.ReactElement<React.ComponentProps<typeof TooltipContent>>;
|
|
61
|
-
/**
|
|
62
|
-
* The unique identifier to give to the tooltip. Provide this in cases where
|
|
63
|
-
* you want to override the default accessibility solution. This identifier
|
|
64
|
-
* will be applied to the tooltip bubble content.
|
|
65
|
-
*
|
|
66
|
-
* By providing this identifier, the children that this tooltip anchors to
|
|
67
|
-
* will not be automatically given the aria-desribedby attribute. Instead,
|
|
68
|
-
* the accessibility solution is the responsibility of the caller.
|
|
69
|
-
*
|
|
70
|
-
* If this is not provided, the aria-describedby attribute will be added
|
|
71
|
-
* to the children with a unique identifier pointing to the tooltip bubble
|
|
72
|
-
* content.
|
|
73
|
-
*/
|
|
74
|
-
id?: string;
|
|
75
|
-
/**
|
|
76
|
-
* When true, if a tabindex attribute is not already present on the element
|
|
77
|
-
* wrapped by the anchor, the element will be given tabindex=0 to make it
|
|
78
|
-
* keyboard focusable; otherwise, does not attempt to change the ability to
|
|
79
|
-
* focus the anchor element.
|
|
80
|
-
*
|
|
81
|
-
* Defaults to true.
|
|
82
|
-
*
|
|
83
|
-
* One might set this to false in circumstances where the wrapped component
|
|
84
|
-
* already can receive focus or contains an element that can.
|
|
85
|
-
* Use good judgement when overriding this value, the tooltip content should
|
|
86
|
-
* be accessible via keyboard in all circumstances where the tooltip would
|
|
87
|
-
* appear using the mouse, so verify those use-cases.
|
|
88
|
-
*
|
|
89
|
-
* Also, note that the aria-describedby attribute is attached to the root
|
|
90
|
-
* anchor element, so you may need to implement an additional accessibility
|
|
91
|
-
* solution when overriding anchor focusivity.
|
|
92
|
-
*/
|
|
93
|
-
forceAnchorFocusivity?: boolean;
|
|
94
|
-
/**
|
|
95
|
-
* Where the tooltip should appear in relation to the anchor element.
|
|
96
|
-
* Defaults to "top".
|
|
97
|
-
*/
|
|
98
|
-
placement: Placement;
|
|
99
|
-
/**
|
|
100
|
-
* Renders the tooltip when true, renders nothing when false.
|
|
101
|
-
*
|
|
102
|
-
* Using this prop makes the component behave as a controlled component. The
|
|
103
|
-
* parent is responsible for managing the opening/closing of the tooltip
|
|
104
|
-
* when using this prop.
|
|
105
|
-
*/
|
|
106
|
-
opened?: boolean;
|
|
107
|
-
/**
|
|
108
|
-
* Test ID used for e2e testing.
|
|
109
|
-
*/
|
|
110
|
-
testId?: string;
|
|
111
|
-
/**
|
|
112
|
-
* Optional custom styles for the tooltip content which are a subset of valid CSS styles.
|
|
113
|
-
*/
|
|
114
|
-
contentStyle?: ContentStyle;
|
|
115
|
-
/**
|
|
116
|
-
* Optional background color.
|
|
117
|
-
*/
|
|
118
|
-
backgroundColor?: keyof typeof color;
|
|
119
|
-
}>;
|
|
120
|
-
|
|
121
|
-
type State = Readonly<{
|
|
122
|
-
/**
|
|
123
|
-
* Whether the tooltip is open by hovering/focusing on the anchor element.
|
|
124
|
-
*/
|
|
125
|
-
active: boolean;
|
|
126
|
-
/**
|
|
127
|
-
* Whether the tooltip is open by hovering on the tooltip bubble.
|
|
128
|
-
*/
|
|
129
|
-
activeBubble: boolean;
|
|
130
|
-
/**
|
|
131
|
-
* The element that activates the tooltip.
|
|
132
|
-
*/
|
|
133
|
-
anchorElement?: HTMLElement;
|
|
134
|
-
}>;
|
|
135
|
-
|
|
136
|
-
type DefaultProps = {
|
|
137
|
-
forceAnchorFocusivity: Props["forceAnchorFocusivity"];
|
|
138
|
-
placement: Props["placement"];
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Use a tooltip to help describe an on screen object.
|
|
143
|
-
*
|
|
144
|
-
* Tooltips:
|
|
145
|
-
* - contain text
|
|
146
|
-
* - (optional) contain small graphic elements to complement the text
|
|
147
|
-
* - appear on hover or focus (for non-assistive tech keyboard users)
|
|
148
|
-
* - must have a tail that points to a parent object
|
|
149
|
-
* - DO NOT include actions
|
|
150
|
-
*
|
|
151
|
-
* For more rich content see Popovers, for taking action on an object, see
|
|
152
|
-
* Snackbars (proposed).
|
|
153
|
-
*
|
|
154
|
-
* ### Usage
|
|
155
|
-
*
|
|
156
|
-
* ```jsx
|
|
157
|
-
* import Tooltip from "@khanacademy/wonder-blocks-tooltip";
|
|
158
|
-
*
|
|
159
|
-
* <Tooltip content="This is a text tooltip">
|
|
160
|
-
* Tooltip anchor
|
|
161
|
-
* </Tooltip>
|
|
162
|
-
* ```
|
|
163
|
-
*
|
|
164
|
-
*/
|
|
165
|
-
export default class Tooltip extends React.Component<Props, State> {
|
|
166
|
-
static defaultProps: DefaultProps = {
|
|
167
|
-
forceAnchorFocusivity: true,
|
|
168
|
-
placement: "top",
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Used to sync the `opened` state when Tooltip acts as a controlled
|
|
173
|
-
* component
|
|
174
|
-
*/
|
|
175
|
-
static getDerivedStateFromProps(
|
|
176
|
-
props: Props,
|
|
177
|
-
state: State,
|
|
178
|
-
): Partial<State> | null {
|
|
179
|
-
return {
|
|
180
|
-
active:
|
|
181
|
-
typeof props.opened === "boolean" ? props.opened : state.active,
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
state: State = {
|
|
186
|
-
active: false,
|
|
187
|
-
activeBubble: false,
|
|
188
|
-
};
|
|
189
|
-
static ariaContentId = "aria-content";
|
|
190
|
-
|
|
191
|
-
_updateAnchorElement(ref?: Element | null) {
|
|
192
|
-
if (ref && ref !== this.state.anchorElement) {
|
|
193
|
-
this.setState({anchorElement: ref as HTMLElement});
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
_renderBubbleContent(): React.ReactElement<
|
|
198
|
-
React.ComponentProps<typeof TooltipContent>
|
|
199
|
-
> {
|
|
200
|
-
const {title, content, contentStyle, testId} = this.props;
|
|
201
|
-
if (typeof content === "string") {
|
|
202
|
-
return (
|
|
203
|
-
<TooltipContent
|
|
204
|
-
title={title}
|
|
205
|
-
contentStyle={contentStyle}
|
|
206
|
-
testId={testId ? `${testId}-content` : undefined}
|
|
207
|
-
>
|
|
208
|
-
{content}
|
|
209
|
-
</TooltipContent>
|
|
210
|
-
);
|
|
211
|
-
} else if (title) {
|
|
212
|
-
return React.cloneElement(content, {title});
|
|
213
|
-
} else {
|
|
214
|
-
return content;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
_renderPopper(ids?: IIdentifierFactory): React.ReactNode {
|
|
219
|
-
const {id, backgroundColor} = this.props;
|
|
220
|
-
const bubbleId = ids ? ids.get(Tooltip.ariaContentId) : id;
|
|
221
|
-
if (!bubbleId) {
|
|
222
|
-
throw new Error("Did not get an identifier factory nor a id prop");
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const {placement} = this.props;
|
|
226
|
-
return (
|
|
227
|
-
<TooltipPopper
|
|
228
|
-
anchorElement={this.state.anchorElement}
|
|
229
|
-
placement={placement}
|
|
230
|
-
autoUpdate={this.props.autoUpdate}
|
|
231
|
-
>
|
|
232
|
-
{(props) => (
|
|
233
|
-
<TooltipBubble
|
|
234
|
-
id={bubbleId}
|
|
235
|
-
style={props.style}
|
|
236
|
-
backgroundColor={backgroundColor}
|
|
237
|
-
tailOffset={props.tailOffset}
|
|
238
|
-
isReferenceHidden={props.isReferenceHidden}
|
|
239
|
-
placement={props.placement}
|
|
240
|
-
updateTailRef={props.updateTailRef}
|
|
241
|
-
updateBubbleRef={props.updateBubbleRef}
|
|
242
|
-
onActiveChanged={(active) =>
|
|
243
|
-
this.setState({activeBubble: active})
|
|
244
|
-
}
|
|
245
|
-
>
|
|
246
|
-
{this._renderBubbleContent()}
|
|
247
|
-
</TooltipBubble>
|
|
248
|
-
)}
|
|
249
|
-
</TooltipPopper>
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
_getHost(): Element | null | undefined {
|
|
254
|
-
const {anchorElement} = this.state;
|
|
255
|
-
|
|
256
|
-
return (
|
|
257
|
-
maybeGetPortalMountedModalHostElement(anchorElement) ||
|
|
258
|
-
document.body
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
_renderTooltipAnchor(ids?: IIdentifierFactory): React.ReactNode {
|
|
263
|
-
const {autoUpdate, children, forceAnchorFocusivity} = this.props;
|
|
264
|
-
const {active, activeBubble} = this.state;
|
|
265
|
-
|
|
266
|
-
const popperHost = this._getHost();
|
|
267
|
-
|
|
268
|
-
// Only render the popper if the anchor element is available so that we
|
|
269
|
-
// can position the popper correctly. If autoUpdate is false, we don't
|
|
270
|
-
// need to wait for the anchor element to render the popper.
|
|
271
|
-
const shouldAnchorExist = autoUpdate ? this.state.anchorElement : true;
|
|
272
|
-
const shouldBeVisible =
|
|
273
|
-
popperHost && (active || activeBubble) && shouldAnchorExist;
|
|
274
|
-
|
|
275
|
-
// TODO(kevinb): update to use ReactPopper's React 16-friendly syntax
|
|
276
|
-
return (
|
|
277
|
-
<React.Fragment>
|
|
278
|
-
<TooltipAnchor
|
|
279
|
-
forceAnchorFocusivity={forceAnchorFocusivity}
|
|
280
|
-
anchorRef={(r) => this._updateAnchorElement(r)}
|
|
281
|
-
onActiveChanged={(active) => this.setState({active})}
|
|
282
|
-
ids={ids}
|
|
283
|
-
>
|
|
284
|
-
{children}
|
|
285
|
-
</TooltipAnchor>
|
|
286
|
-
{shouldBeVisible &&
|
|
287
|
-
ReactDOM.createPortal(this._renderPopper(ids), popperHost)}
|
|
288
|
-
</React.Fragment>
|
|
289
|
-
);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
render(): React.ReactNode {
|
|
293
|
-
const {id} = this.props;
|
|
294
|
-
if (id) {
|
|
295
|
-
// Let's bypass the extra weight of an id provider since we don't
|
|
296
|
-
// need it.
|
|
297
|
-
return this._renderTooltipAnchor();
|
|
298
|
-
} else {
|
|
299
|
-
return (
|
|
300
|
-
<UniqueIDProvider scope="tooltip" mockOnFirstRender={true}>
|
|
301
|
-
{(ids) => this._renderTooltipAnchor(ids)}
|
|
302
|
-
</UniqueIDProvider>
|
|
303
|
-
);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type {Placement, PopperElementProps} from "./util/types";
|
|
2
|
-
|
|
3
|
-
import Tooltip from "./components/tooltip";
|
|
4
|
-
import TooltipContent from "./components/tooltip-content";
|
|
5
|
-
import TooltipPopper from "./components/tooltip-popper";
|
|
6
|
-
import TooltipTail from "./components/tooltip-tail";
|
|
7
|
-
|
|
8
|
-
export {Tooltip as default, TooltipContent, TooltipPopper, TooltipTail};
|
|
9
|
-
|
|
10
|
-
export type {Placement, PopperElementProps};
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import ActiveTracker from "../active-tracker";
|
|
2
|
-
import type {IActiveTrackerSubscriber} from "../active-tracker";
|
|
3
|
-
|
|
4
|
-
class MockSubscriber implements IActiveTrackerSubscriber {
|
|
5
|
-
activeStateStolen = jest.fn();
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
describe("ActiveTracker", () => {
|
|
9
|
-
describe("#subscribe", () => {
|
|
10
|
-
test("subscribes to notifications", () => {
|
|
11
|
-
// Arrange
|
|
12
|
-
const tracker = new ActiveTracker();
|
|
13
|
-
const subscriber = new MockSubscriber();
|
|
14
|
-
const thief = new MockSubscriber();
|
|
15
|
-
|
|
16
|
-
// Act
|
|
17
|
-
tracker.subscribe(subscriber);
|
|
18
|
-
tracker.steal(thief);
|
|
19
|
-
|
|
20
|
-
// Assert
|
|
21
|
-
expect(subscriber.activeStateStolen).toHaveBeenCalledTimes(1);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test("if already subscribed, throws", () => {
|
|
25
|
-
// Arrange
|
|
26
|
-
const tracker = new ActiveTracker();
|
|
27
|
-
const subscriber = new MockSubscriber();
|
|
28
|
-
tracker.subscribe(subscriber);
|
|
29
|
-
|
|
30
|
-
// Act
|
|
31
|
-
const underTest = () => tracker.subscribe(subscriber);
|
|
32
|
-
|
|
33
|
-
// Assert
|
|
34
|
-
expect(underTest).toThrowErrorMatchingSnapshot();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test("returns a function", () => {
|
|
38
|
-
// Arrange
|
|
39
|
-
const tracker = new ActiveTracker();
|
|
40
|
-
const subscriber = new MockSubscriber();
|
|
41
|
-
|
|
42
|
-
// Act
|
|
43
|
-
const result = tracker.subscribe(subscriber);
|
|
44
|
-
|
|
45
|
-
// Assert
|
|
46
|
-
expect(result).toBeInstanceOf(Function);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("returned function unsubscribes from notifications", () => {
|
|
50
|
-
// Arrange
|
|
51
|
-
const tracker = new ActiveTracker();
|
|
52
|
-
const subscriber1 = new MockSubscriber();
|
|
53
|
-
const testCase = new MockSubscriber();
|
|
54
|
-
const subscriber3 = new MockSubscriber();
|
|
55
|
-
const subscriber4 = new MockSubscriber();
|
|
56
|
-
tracker.subscribe(subscriber1);
|
|
57
|
-
const unsubscribe = tracker.subscribe(testCase);
|
|
58
|
-
tracker.subscribe(subscriber3);
|
|
59
|
-
|
|
60
|
-
// Act
|
|
61
|
-
unsubscribe();
|
|
62
|
-
tracker.steal(subscriber4);
|
|
63
|
-
|
|
64
|
-
// Assert
|
|
65
|
-
expect(testCase.activeStateStolen).not.toHaveBeenCalled();
|
|
66
|
-
expect(subscriber1.activeStateStolen).toHaveBeenCalledTimes(1);
|
|
67
|
-
expect(subscriber3.activeStateStolen).toHaveBeenCalledTimes(1);
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
describe("#steal", () => {
|
|
72
|
-
test("notifies subscribers of theft attempt", () => {
|
|
73
|
-
// Arrange
|
|
74
|
-
const tracker = new ActiveTracker();
|
|
75
|
-
const thief = new MockSubscriber();
|
|
76
|
-
const subscriber = new MockSubscriber();
|
|
77
|
-
tracker.subscribe(subscriber);
|
|
78
|
-
|
|
79
|
-
// Act
|
|
80
|
-
tracker.steal(thief);
|
|
81
|
-
|
|
82
|
-
// Assert
|
|
83
|
-
expect(subscriber.activeStateStolen).toHaveBeenCalledTimes(1);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
test("does not notifier thief of their own theft attempt", () => {
|
|
87
|
-
// Arrange
|
|
88
|
-
const tracker = new ActiveTracker();
|
|
89
|
-
const thief = new MockSubscriber();
|
|
90
|
-
tracker.subscribe(thief);
|
|
91
|
-
|
|
92
|
-
// Act
|
|
93
|
-
tracker.steal(thief);
|
|
94
|
-
|
|
95
|
-
// Assert
|
|
96
|
-
expect(thief.activeStateStolen).not.toHaveBeenCalledTimes(1);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test("returns falsy if active state was not stolen", () => {
|
|
100
|
-
// Arrange
|
|
101
|
-
const tracker = new ActiveTracker();
|
|
102
|
-
const thief = new MockSubscriber();
|
|
103
|
-
|
|
104
|
-
// Act
|
|
105
|
-
const result = tracker.steal(thief);
|
|
106
|
-
|
|
107
|
-
// Assert
|
|
108
|
-
expect(result).toBeFalsy();
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test("returns truthy if active state was stolen", () => {
|
|
112
|
-
// Arrange
|
|
113
|
-
const tracker = new ActiveTracker();
|
|
114
|
-
const thief = new MockSubscriber();
|
|
115
|
-
const owner = new MockSubscriber();
|
|
116
|
-
tracker.steal(owner);
|
|
117
|
-
|
|
118
|
-
// Act
|
|
119
|
-
const result = tracker.steal(thief);
|
|
120
|
-
|
|
121
|
-
// Assert
|
|
122
|
-
expect(result).toBeTruthy();
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
describe("#giveup", () => {
|
|
127
|
-
test("marks the active state as false", () => {
|
|
128
|
-
// Arrange
|
|
129
|
-
const tracker = new ActiveTracker();
|
|
130
|
-
const owner = new MockSubscriber();
|
|
131
|
-
tracker.steal(owner);
|
|
132
|
-
|
|
133
|
-
// Act
|
|
134
|
-
expect(tracker.steal(owner)).toBeTruthy();
|
|
135
|
-
tracker.giveup();
|
|
136
|
-
const result = tracker.steal(owner);
|
|
137
|
-
|
|
138
|
-
// Assert
|
|
139
|
-
expect(result).toBeFalsy();
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
});
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import * as ReactDOM from "react-dom";
|
|
3
|
-
import {render} from "@testing-library/react";
|
|
4
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
5
|
-
|
|
6
|
-
import RefTracker from "../ref-tracker";
|
|
7
|
-
|
|
8
|
-
type CallbackFn = (arg1?: HTMLElement | null | undefined) => void;
|
|
9
|
-
|
|
10
|
-
describe("RefTracker", () => {
|
|
11
|
-
describe("#setCallback", () => {
|
|
12
|
-
test("called with falsy value, no throw", () => {
|
|
13
|
-
// Arrange
|
|
14
|
-
const tracker = new RefTracker();
|
|
15
|
-
|
|
16
|
-
// Act
|
|
17
|
-
const underTest = () => tracker.setCallback(null);
|
|
18
|
-
|
|
19
|
-
// Assert
|
|
20
|
-
expect(underTest).not.toThrow();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
test("called with non-function, throws", () => {
|
|
24
|
-
// Arrange
|
|
25
|
-
const tracker = new RefTracker();
|
|
26
|
-
const targetFn = {} as CallbackFn;
|
|
27
|
-
|
|
28
|
-
// Act
|
|
29
|
-
const underTest = () => tracker.setCallback(targetFn);
|
|
30
|
-
|
|
31
|
-
// Assert
|
|
32
|
-
expect(underTest).toThrowErrorMatchingSnapshot();
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe("called with a function", () => {
|
|
36
|
-
test("no prior call to updateRef, does not call targetFn", () => {
|
|
37
|
-
// Arrange
|
|
38
|
-
const tracker = new RefTracker();
|
|
39
|
-
const targetFn = jest.fn();
|
|
40
|
-
// Act
|
|
41
|
-
tracker.setCallback(targetFn);
|
|
42
|
-
|
|
43
|
-
// Assert
|
|
44
|
-
expect(targetFn).not.toHaveBeenCalled();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test("prior updateRef call, target called with ref's node", async () => {
|
|
48
|
-
// Arrange
|
|
49
|
-
const tracker = new RefTracker();
|
|
50
|
-
const targetFn = jest.fn();
|
|
51
|
-
const ref = await new Promise((resolve: any) => {
|
|
52
|
-
const nodes = (
|
|
53
|
-
<View>
|
|
54
|
-
<View ref={resolve} />
|
|
55
|
-
</View>
|
|
56
|
-
);
|
|
57
|
-
render(nodes);
|
|
58
|
-
});
|
|
59
|
-
// @ts-expect-error [FEI-5019] - TS2345 - Argument of type 'unknown' is not assignable to parameter of type 'ReactInstance | null | undefined'.
|
|
60
|
-
const domNode = ReactDOM.findDOMNode(ref);
|
|
61
|
-
// @ts-expect-error [FEI-5019] - TS2345 - Argument of type 'unknown' is not assignable to parameter of type 'Element | Component<any, {}, any> | null | undefined'.
|
|
62
|
-
tracker.updateRef(ref);
|
|
63
|
-
|
|
64
|
-
// Act
|
|
65
|
-
tracker.setCallback(targetFn);
|
|
66
|
-
|
|
67
|
-
// Assert
|
|
68
|
-
expect(targetFn).toHaveBeenCalledWith(domNode);
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe("#updateRef", () => {
|
|
74
|
-
describe("calling without setting a callback", () => {
|
|
75
|
-
test("falsy ref, no throw", () => {
|
|
76
|
-
// Arrange
|
|
77
|
-
const tracker = new RefTracker();
|
|
78
|
-
|
|
79
|
-
// Act
|
|
80
|
-
const underTest = () => tracker.updateRef(null);
|
|
81
|
-
|
|
82
|
-
// Assert
|
|
83
|
-
expect(underTest).not.toThrow();
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
test("real ref, no callback, no throw", async () => {
|
|
87
|
-
// Arrange
|
|
88
|
-
const tracker = new RefTracker();
|
|
89
|
-
const ref = await new Promise((resolve: any) => {
|
|
90
|
-
const nodes = (
|
|
91
|
-
<View>
|
|
92
|
-
<View ref={resolve} />
|
|
93
|
-
</View>
|
|
94
|
-
);
|
|
95
|
-
render(nodes);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// Act
|
|
99
|
-
// @ts-expect-error [FEI-5019] - TS2345 - Argument of type 'unknown' is not assignable to parameter of type 'Element | Component<any, {}, any> | null | undefined'.
|
|
100
|
-
const underTest = () => tracker.updateRef(ref);
|
|
101
|
-
|
|
102
|
-
// Assert
|
|
103
|
-
expect(underTest).not.toThrow();
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test("real ref, targetFn callback, calls the targetFn", async () => {
|
|
107
|
-
// Arrange
|
|
108
|
-
const tracker = new RefTracker();
|
|
109
|
-
const targetFn = jest.fn();
|
|
110
|
-
tracker.setCallback(targetFn);
|
|
111
|
-
|
|
112
|
-
const ref = await new Promise((resolve: any) => {
|
|
113
|
-
const nodes = (
|
|
114
|
-
<View>
|
|
115
|
-
<View ref={resolve} />
|
|
116
|
-
</View>
|
|
117
|
-
);
|
|
118
|
-
render(nodes);
|
|
119
|
-
});
|
|
120
|
-
// @ts-expect-error [FEI-5019] - TS2345 - Argument of type 'unknown' is not assignable to parameter of type 'ReactInstance | null | undefined'.
|
|
121
|
-
const domNode = ReactDOM.findDOMNode(ref);
|
|
122
|
-
|
|
123
|
-
// Act
|
|
124
|
-
// @ts-expect-error [FEI-5019] - TS2345 - Argument of type 'unknown' is not assignable to parameter of type 'Element | Component<any, {}, any> | null | undefined'.
|
|
125
|
-
tracker.updateRef(ref);
|
|
126
|
-
|
|
127
|
-
// Assert
|
|
128
|
-
expect(targetFn).toHaveBeenCalledWith(domNode);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
test("same ref, targetFn callback, does not call targetFn", async () => {
|
|
132
|
-
// Arrange
|
|
133
|
-
const tracker = new RefTracker();
|
|
134
|
-
const targetFn = jest.fn();
|
|
135
|
-
tracker.setCallback(targetFn);
|
|
136
|
-
|
|
137
|
-
const ref = await new Promise((resolve: any) => {
|
|
138
|
-
const nodes = (
|
|
139
|
-
<View>
|
|
140
|
-
<View ref={resolve} />
|
|
141
|
-
</View>
|
|
142
|
-
);
|
|
143
|
-
render(nodes);
|
|
144
|
-
});
|
|
145
|
-
// @ts-expect-error [FEI-5019] - TS2345 - Argument of type 'unknown' is not assignable to parameter of type 'Element | Component<any, {}, any> | null | undefined'.
|
|
146
|
-
tracker.updateRef(ref);
|
|
147
|
-
targetFn.mockClear();
|
|
148
|
-
|
|
149
|
-
// Act
|
|
150
|
-
// @ts-expect-error [FEI-5019] - TS2345 - Argument of type 'unknown' is not assignable to parameter of type 'Element | Component<any, {}, any> | null | undefined'.
|
|
151
|
-
tracker.updateRef(ref);
|
|
152
|
-
|
|
153
|
-
// Assert
|
|
154
|
-
expect(targetFn).not.toHaveBeenCalled();
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
});
|