mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
415 lines
10 KiB
TypeScript
415 lines
10 KiB
TypeScript
/**
|
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
*/
|
|
|
|
import { render } from "@testing-library/react";
|
|
import type { RenderResult } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import assert from "assert";
|
|
import React from "react";
|
|
import { computeNextPosition, RectangleDimensions } from "./helpers";
|
|
import { Tooltip, TooltipPosition } from "./tooltip";
|
|
|
|
const getRectangle = (
|
|
parts: Omit<RectangleDimensions, "width" | "height">,
|
|
): RectangleDimensions => {
|
|
assert(parts.right >= parts.left);
|
|
assert(parts.bottom >= parts.top);
|
|
|
|
return {
|
|
...parts,
|
|
width: parts.right - parts.left,
|
|
height: parts.bottom - parts.top,
|
|
};
|
|
};
|
|
|
|
describe("<Tooltip />", () => {
|
|
let requestAnimationFrameSpy: jest.SpyInstance<number, [callback: FrameRequestCallback]>;
|
|
|
|
beforeEach(() => {
|
|
requestAnimationFrameSpy = jest.spyOn(window, "requestAnimationFrame");
|
|
|
|
requestAnimationFrameSpy.mockImplementation((cb) => {
|
|
cb(0);
|
|
|
|
return 0;
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
requestAnimationFrameSpy.mockRestore();
|
|
});
|
|
|
|
it("does not render to DOM if not visible", () => {
|
|
const result = render(
|
|
<>
|
|
<Tooltip targetId="my-target" usePortal={false}>
|
|
I am a tooltip
|
|
</Tooltip>
|
|
<div id="my-target">Target Text</div>
|
|
</>,
|
|
);
|
|
|
|
expect(result.baseElement).toMatchSnapshot();
|
|
});
|
|
|
|
it("renders to DOM when hovering over target", () => {
|
|
const result = render(
|
|
<>
|
|
<Tooltip targetId="my-target" data-testid="tooltip" usePortal={false}>
|
|
I am a tooltip
|
|
</Tooltip>
|
|
<div id="my-target">Target Text</div>
|
|
</>,
|
|
);
|
|
|
|
const target = result.baseElement.querySelector("#my-target");
|
|
|
|
assert(target);
|
|
|
|
userEvent.hover(target);
|
|
expect(result.baseElement).toMatchSnapshot();
|
|
});
|
|
|
|
it("renders to DOM when forced to by visible prop", () => {
|
|
const result = render(
|
|
<>
|
|
<Tooltip targetId="my-target" data-testid="tooltip" visible={true} usePortal={false}>
|
|
I am a tooltip
|
|
</Tooltip>
|
|
<div id="my-target">Target Text</div>
|
|
</>,
|
|
);
|
|
|
|
expect(result.baseElement).toMatchSnapshot();
|
|
});
|
|
|
|
it("uses a portal if usePortal is specified", () => {
|
|
const result = render(
|
|
<div>
|
|
<Tooltip targetId="my-target" data-testid="tooltip" visible={true} usePortal>
|
|
I am a tooltip
|
|
</Tooltip>
|
|
<div id="my-target">Target Text</div>
|
|
</div>,
|
|
);
|
|
|
|
expect(result.baseElement).toMatchSnapshot();
|
|
});
|
|
|
|
describe("when specifying a tooltip for a component", () => {
|
|
let result: RenderResult;
|
|
|
|
beforeEach(() => {
|
|
result = render(
|
|
<div>
|
|
<Tooltip targetId="my-target" data-testid="tooltip">
|
|
I am a tooltip
|
|
</Tooltip>
|
|
<div id="my-target" data-testid="target">
|
|
Target Text
|
|
</div>
|
|
</div>,
|
|
);
|
|
});
|
|
|
|
it("renders", () => {
|
|
expect(result.baseElement).toMatchSnapshot();
|
|
});
|
|
|
|
it("doesn't render the tooltip", () => {
|
|
expect(result.queryByTestId("tooltip")).not.toBeInTheDocument();
|
|
});
|
|
|
|
describe("when hovering over the target element", () => {
|
|
beforeEach(() => {
|
|
userEvent.hover(result.getByTestId("target"));
|
|
});
|
|
|
|
it("renders", () => {
|
|
expect(result.baseElement).toMatchSnapshot();
|
|
});
|
|
|
|
it("renders the tooltip", () => {
|
|
expect(result.getByTestId("tooltip")).toBeInTheDocument();
|
|
});
|
|
|
|
describe("when no longer hovering the target", () => {
|
|
beforeEach(() => {
|
|
userEvent.unhover(result.getByTestId("target"));
|
|
});
|
|
|
|
it("renders", () => {
|
|
expect(result.baseElement).toMatchSnapshot();
|
|
});
|
|
|
|
it("doesn't render the tooltip", () => {
|
|
expect(result.queryByTestId("tooltip")).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("when specifying a tooltip for a component with show on parent hover", () => {
|
|
let result: RenderResult;
|
|
|
|
beforeEach(() => {
|
|
result = render(
|
|
<div>
|
|
<Tooltip targetId="my-target" data-testid="tooltip" tooltipOnParentHover>
|
|
I am a tooltip
|
|
</Tooltip>
|
|
<div data-testid="target-parent">
|
|
<div id="my-target" data-testid="target">
|
|
Target Text
|
|
</div>
|
|
</div>
|
|
</div>,
|
|
);
|
|
});
|
|
|
|
it("renders", () => {
|
|
expect(result.baseElement).toMatchSnapshot();
|
|
});
|
|
|
|
it("doesn't render the tooltip", () => {
|
|
expect(result.queryByTestId("tooltip")).not.toBeInTheDocument();
|
|
});
|
|
|
|
describe("when hovering over the target element", () => {
|
|
beforeEach(() => {
|
|
userEvent.hover(result.getByTestId("target"));
|
|
});
|
|
|
|
it("renders", () => {
|
|
expect(result.baseElement).toMatchSnapshot();
|
|
});
|
|
|
|
it("renders the tooltip", () => {
|
|
expect(result.getByTestId("tooltip")).toBeInTheDocument();
|
|
});
|
|
|
|
describe("when no longer hovering the target", () => {
|
|
beforeEach(() => {
|
|
userEvent.unhover(result.getByTestId("target"));
|
|
});
|
|
|
|
it("renders", () => {
|
|
expect(result.baseElement).toMatchSnapshot();
|
|
});
|
|
|
|
it("doesn't render the tooltip", () => {
|
|
expect(result.queryByTestId("tooltip")).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("when hovering over the target's parent element", () => {
|
|
beforeEach(() => {
|
|
userEvent.hover(result.getByTestId("target-parent"));
|
|
});
|
|
|
|
it("renders", () => {
|
|
expect(result.baseElement).toMatchSnapshot();
|
|
});
|
|
|
|
it("renders the tooltip", () => {
|
|
expect(result.getByTestId("tooltip")).toBeInTheDocument();
|
|
});
|
|
|
|
describe("when no longer hovering the target's parent", () => {
|
|
beforeEach(() => {
|
|
userEvent.unhover(result.getByTestId("target-parent"));
|
|
});
|
|
|
|
it("renders", () => {
|
|
expect(result.baseElement).toMatchSnapshot();
|
|
});
|
|
|
|
it("doesn't render the tooltip", () => {
|
|
expect(result.queryByTestId("tooltip")).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("when specifying a tooltip for a component that doesn't exist with show on parent hover", () => {
|
|
let result: RenderResult;
|
|
|
|
beforeEach(() => {
|
|
result = render(
|
|
<>
|
|
<Tooltip targetId="my-target" data-testid="tooltip" tooltipOnParentHover>
|
|
I am a tooltip
|
|
</Tooltip>
|
|
</>,
|
|
);
|
|
});
|
|
|
|
it("renders", () => {
|
|
expect(result.baseElement).toMatchSnapshot();
|
|
});
|
|
|
|
it("doesn't render the tooltip", () => {
|
|
expect(result.queryByTestId("tooltip")).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("computeNextPosition technical tests", () => {
|
|
describe("given a 1280x720 window", () => {
|
|
beforeEach(() => {
|
|
window.innerHeight = 720;
|
|
window.innerWidth = 1280;
|
|
});
|
|
|
|
[
|
|
{
|
|
position: "right",
|
|
targetRect: {
|
|
top: 700,
|
|
left: 700,
|
|
bottom: 720,
|
|
right: 720,
|
|
},
|
|
},
|
|
{
|
|
position: "bottom",
|
|
targetRect: {
|
|
top: 500,
|
|
left: 1200,
|
|
bottom: 520,
|
|
right: 1280,
|
|
},
|
|
},
|
|
{
|
|
position: "top",
|
|
targetRect: {
|
|
top: 700,
|
|
left: 1200,
|
|
bottom: 720,
|
|
right: 1280,
|
|
},
|
|
},
|
|
{
|
|
position: "left",
|
|
targetRect: {
|
|
top: 0,
|
|
left: 1200,
|
|
bottom: 720,
|
|
right: 1280,
|
|
},
|
|
},
|
|
].forEach(({ position, targetRect }) => {
|
|
it(`renders to the "${position}" by default if there is enough room`, () => {
|
|
expect(
|
|
computeNextPosition({
|
|
offset: 10,
|
|
target: {
|
|
getBoundingClientRect: () => getRectangle(targetRect),
|
|
},
|
|
tooltip: {
|
|
getBoundingClientRect: () => ({
|
|
height: 20,
|
|
width: 20,
|
|
}),
|
|
},
|
|
}),
|
|
).toMatchObject({
|
|
position,
|
|
});
|
|
});
|
|
});
|
|
|
|
it("doesn't throw if the preferredPosition is invalid", () => {
|
|
expect(
|
|
computeNextPosition({
|
|
preferredPositions: "some-invalid-data" as any,
|
|
offset: 10,
|
|
target: {
|
|
getBoundingClientRect: () =>
|
|
getRectangle({
|
|
top: 50,
|
|
left: 50,
|
|
bottom: 100,
|
|
right: 100,
|
|
}),
|
|
},
|
|
tooltip: {
|
|
getBoundingClientRect: () => ({
|
|
height: 20,
|
|
width: 20,
|
|
}),
|
|
},
|
|
}),
|
|
).toMatchObject({
|
|
position: "right",
|
|
});
|
|
});
|
|
|
|
it("defaults to right if no other location works", () => {
|
|
expect(
|
|
computeNextPosition({
|
|
offset: 10,
|
|
target: {
|
|
getBoundingClientRect: () =>
|
|
getRectangle({
|
|
top: 0,
|
|
left: 0,
|
|
bottom: 720,
|
|
right: 1280,
|
|
}),
|
|
},
|
|
tooltip: {
|
|
getBoundingClientRect: () => ({
|
|
height: 20,
|
|
width: 20,
|
|
}),
|
|
},
|
|
}),
|
|
).toMatchObject({
|
|
position: "right",
|
|
});
|
|
});
|
|
|
|
it.each([
|
|
TooltipPosition.RIGHT,
|
|
TooltipPosition.BOTTOM,
|
|
TooltipPosition.TOP,
|
|
TooltipPosition.LEFT,
|
|
TooltipPosition.TOP_RIGHT,
|
|
TooltipPosition.TOP_LEFT,
|
|
TooltipPosition.BOTTOM_RIGHT,
|
|
TooltipPosition.BOTTOM_LEFT,
|
|
])(
|
|
"computes to the %p if there is space and it is specified as a preferred position",
|
|
(position) => {
|
|
expect(
|
|
computeNextPosition({
|
|
preferredPositions: position,
|
|
offset: 10,
|
|
target: {
|
|
getBoundingClientRect: () =>
|
|
getRectangle({
|
|
top: 50,
|
|
left: 50,
|
|
bottom: 100,
|
|
right: 100,
|
|
}),
|
|
},
|
|
tooltip: {
|
|
getBoundingClientRect: () => ({
|
|
height: 20,
|
|
width: 20,
|
|
}),
|
|
},
|
|
}),
|
|
).toMatchObject({
|
|
position,
|
|
});
|
|
},
|
|
);
|
|
});
|
|
});
|