/**
* Copyright 2021 Palantir Technologies, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable @typescript-eslint/no-deprecated */
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import {
afterAll,
afterEach,
beforeAll,
beforeEach,
describe,
expect,
it,
type MockInstance,
vi,
} from "@blueprintjs/test-commons/vitest";
import { Button, PopupKind, Tooltip } from "..";
import { Classes } from "../../common";
import * as Errors from "../../common/errors";
import * as Utils from "../../common/utils";
import { Popover } from "./popover";
import { type PopoverInteractionKind } from "./popoverProps";
describe("", () => {
describe("validation", () => {
let warnSpy: MockInstance;
// use vi.spyOn to prevent warnings from appearing in the test logs
beforeAll(() => (warnSpy = vi.spyOn(console, "warn").mockImplementation(vi.fn())));
beforeEach(() => warnSpy.mockClear());
afterAll(() => warnSpy.mockRestore());
it("throws error if given no target", () => {
render();
expect(warnSpy).toHaveBeenCalledWith(Errors.POPOVER_REQUIRES_TARGET);
});
it("warns if given > 1 target elements", () => {
render(
,
);
expect(warnSpy).toHaveBeenCalledWith(Errors.POPOVER_WARN_TOO_MANY_CHILDREN);
});
it("warns if given children and renderTarget prop", () => {
render( "boom"}>pow);
expect(warnSpy).toHaveBeenCalledWith(Errors.POPOVER_WARN_DOUBLE_TARGET);
});
it("warns if given targetProps and renderTarget", () => {
render( "boom"} />);
expect(warnSpy).toHaveBeenCalledWith(Errors.POPOVER_WARN_TARGET_PROPS_WITH_RENDER_TARGET);
});
it("warns if attempting to open a popover with empty content", () => {
render(
{"target"}
,
);
expect(warnSpy).toHaveBeenCalledWith(Errors.POPOVER_WARN_EMPTY_CONTENT);
});
it("warns if backdrop enabled when rendering inline", () => {
render(
{"target"}
,
);
expect(warnSpy).toHaveBeenCalledWith(Errors.POPOVER_WARN_HAS_BACKDROP_INLINE);
});
it("warns and disables if given undefined content", async () => {
const { container } = render(
,
);
expect(container.querySelector(`.${Classes.OVERLAY}`)).not.toBeInTheDocument();
expect(warnSpy).toHaveBeenCalledWith(Errors.POPOVER_WARN_EMPTY_CONTENT);
});
it("warns and disables if given empty string content", () => {
const EMPTY_STRING = " ";
const { container } = render(
,
);
expect(container.querySelector(`.${Classes.OVERLAY}`)).not.toBeInTheDocument();
expect(warnSpy).toHaveBeenCalledWith(Errors.POPOVER_WARN_EMPTY_CONTENT);
});
describe("throws error if backdrop enabled with non-CLICK interactionKind", () => {
runErrorTest("hover");
runErrorTest("hover-target");
runErrorTest("click-target");
it("doesn't throw error for CLICK", () => {
expect(() => ).to.not.throw;
});
function runErrorTest(interactionKind: PopoverInteractionKind) {
it(interactionKind, () => {
render(
} hasBackdrop={true} interactionKind={interactionKind}>
,
);
expect(warnSpy).toHaveBeenCalledWith(Errors.POPOVER_HAS_BACKDROP_INTERACTION);
});
}
});
});
describe("React 19 deprecation warning", () => {
afterEach(() => vi.restoreAllMocks());
it("does not warn on React < 19", () => {
const warnSpy = vi.spyOn(console, "warn").mockImplementation(vi.fn());
vi.spyOn(Utils, "getReactMajorVersion").mockReturnValue(18);
render(
,
);
expect(warnSpy).not.toHaveBeenCalledWith(Errors.POPOVER_WARN_REACT19);
});
it("warns once on React 19", () => {
const warnSpy = vi.spyOn(console, "warn").mockImplementation(vi.fn());
vi.spyOn(Utils, "getReactMajorVersion").mockReturnValue(19);
render(
,
);
expect(warnSpy).toHaveBeenCalledWith(Errors.POPOVER_WARN_REACT19);
});
});
describe("rendering", () => {
it("adds POPOVER_OPEN class to target when the popover is open", async () => {
const user = userEvent.setup();
const { container } = render(
,
);
const popoverTarget = container.querySelector(`.${Classes.POPOVER_TARGET}`);
expect(popoverTarget).to.exist;
expect(popoverTarget).not.toHaveClass(Classes.POPOVER_OPEN);
await user.click(screen.getByRole("button", { name: "target" }));
await waitFor(() => expect(popoverTarget).toHaveClass(Classes.POPOVER_OPEN));
});
it("renders Portal when usePortal=true", async () => {
const user = userEvent.setup();
const { baseElement } = render(
,
);
await user.click(screen.getByRole("button", { name: "target" }));
await waitFor(() => expect(screen.getByText("content")).to.exist);
expect(baseElement.querySelector(`.${Classes.PORTAL}`)).to.exist;
});
it("renders to specified container correctly", async () => {
// setup: create a container
const container = document.createElement("div");
document.body.appendChild(container);
render(
,
);
await waitFor(() => expect(screen.getByText("content")).to.exist);
expect(container.querySelector(`.${Classes.POPOVER_CONTENT}`)).to.exist;
// cleanup
document.body.removeChild(container);
});
it("does not render Portal when usePortal=false", async () => {
const user = userEvent.setup();
const { container } = render(
,
);
await user.click(screen.getByRole("button", { name: "target" }));
await waitFor(() => expect(screen.getByText("content")).to.exist);
expect(container.querySelector(`.${Classes.PORTAL}`)).not.toBeInTheDocument();
});
it("hasBackdrop=true renders backdrop element", async () => {
const { baseElement } = render(
,
);
await waitFor(() => expect(screen.getByText("content")).to.exist);
expect(baseElement.querySelector(`.${Classes.POPOVER_BACKDROP}`)).to.exist;
});
it("hasBackdrop=false does not render backdrop element", async () => {
const { container } = render(
,
);
await waitFor(() => expect(screen.getByText("content")).to.exist);
expect(container.querySelector(`.${Classes.POPOVER_BACKDROP}`)).not.toBeInTheDocument();
});
it("targetTagName renders the right elements", () => {
const { container } = render(
,
);
const popoverTarget = container.querySelector(`.${Classes.POPOVER_TARGET}`);
expect(popoverTarget).to.exist;
expect(popoverTarget!.tagName).to.equal("ADDRESS");
});
it("allows user to apply dark theme explicitly", () => {
const { container } = render(
,
);
const popoverElement = container.querySelector(`.${Classes.POPOVER}`);
expect(popoverElement).to.exist;
expect(popoverElement).toHaveClass(Classes.DARK);
});
it("renders with aria-haspopup attr", () => {
const { container } = render(
,
);
expect(container.querySelector("[aria-haspopup='menu']")).to.exist;
});
it("sets aria-haspopup attr base on popupKind", () => {
const { container } = render(
,
);
expect(container.querySelector("[aria-haspopup='dialog']")).to.exist;
});
it("renders without aria-haspopup attr for hover interaction", () => {
const { container } = render(
,
);
expect(container.querySelector("[aria-haspopup]")).not.toBeInTheDocument();
});
it("applies fill class to target when fill={true}", () => {
const { container } = render(
,
);
const popoverTarget = container.querySelector(`.${Classes.POPOVER_TARGET}`);
expect(popoverTarget).to.exist;
expect(popoverTarget).toHaveClass(Classes.FILL);
});
it("does not apply FILL class to target when fill={false}", () => {
const { container } = render(
,
);
const popoverTarget = container.querySelector(`.${Classes.POPOVER_TARGET}`);
expect(popoverTarget).to.exist;
expect(popoverTarget).not.toHaveClass(Classes.FILL);
});
});
describe("basic functionality", () => {
it.skip("inherits dark theme from trigger ancestor", () => {
const { baseElement } = render(
,
);
const popoverElement = baseElement.querySelector(`.${Classes.POPOVER}`);
expect(popoverElement).to.exist;
expect(popoverElement).toHaveClass(Classes.DARK);
});
it("inheritDarkTheme=false disables inheriting dark theme from trigger ancestor", () => {
const { baseElement } = render(
,
);
const popoverElement = baseElement.querySelector(`.${Classes.POPOVER}`);
expect(popoverElement).to.exist;
expect(popoverElement).not.toHaveClass(Classes.DARK);
});
it("supports overlay lifecycle props", async () => {
const user = userEvent.setup();
const onOpening = vi.fn();
render(
,
);
await user.click(screen.getByRole("button", { name: "target" }));
expect(onOpening).toHaveBeenCalledOnce();
});
it.skip("escape key closes popover", async () => {
const user = userEvent.setup();
render(
,
);
await user.click(screen.getByRole("button", { name: "target" }));
await waitFor(() => {
expect(screen.getByText("content")).to.exist;
});
await user.keyboard("{Escape}");
await waitFor(() => {
expect(screen.queryByText("content")).not.toBeInTheDocument();
});
});
});
describe("focus management when shouldReturnFocusOnClose={true}", () => {
it("moves focus to overlay when opened and returns focus to target element when closed", async () => {
const user = userEvent.setup();
const { container } = render(
close}
shouldReturnFocusOnClose={true}
usePortal={false}
>
,
);
const targetButton = screen.getByRole("button", { name: "target" });
await user.click(targetButton);
const overlay = container.querySelector(`.${Classes.OVERLAY}`);
await waitFor(() => {
expect(overlay).to.exist;
expect(overlay).toHaveClass(Classes.OVERLAY_OPEN);
expect(overlay?.contains(document.activeElement)).to.be.true;
});
const closeButton = screen.getByRole("button", { name: "close" });
await user.click(closeButton);
await waitFor(() => {
expect(overlay).not.toHaveClass(Classes.OVERLAY_OPEN);
expect(targetButton).to.equal(document.activeElement);
});
});
});
describe("openOnTargetFocus", () => {
describe("if true (default)", () => {
it('adds tabindex="0" to target\'s child node when interactionKind is HOVER', () => {
render(
,
);
expect(screen.getByRole("button", { name: "target" }).getAttribute("tabindex")).to.equal("0");
});
it('adds tabindex="0" to target\'s child node when interactionKind is HOVER_TARGET_ONLY', () => {
render(
,
);
expect(screen.getByRole("button", { name: "target" }).getAttribute("tabindex")).to.equal("0");
});
it("does not add tabindex to target's child node when interactionKind is CLICK", () => {
render(
,
);
expect(screen.getByRole("button", { name: "target" }).getAttribute("tabindex")).not.toBeInTheDocument();
});
it("does not add tabindex to target's child node when interactionKind is CLICK_TARGET_ONLY", () => {
render(
,
);
expect(screen.getByRole("button", { name: "target" }).getAttribute("tabindex")).not.toBeInTheDocument();
});
it("does not add tabindex to target's child node when disabled=true", () => {
render(
,
);
expect(screen.getByRole("button", { name: "target" }).getAttribute("tabindex")).not.toBeInTheDocument();
});
it.skip("opens popover on target focus when interactionKind is HOVER", async () => {
render(
,
);
const targetButton = screen.getByRole("button", { name: "target" });
targetButton.focus();
await waitFor(() => expect(screen.getByText("content")).to.exist);
expect(targetButton).to.equal(document.activeElement);
});
it.skip("opens popover on target focus when interactionKind is HOVER_TARGET_ONLY", async () => {
render(
,
);
const targetButton = screen.getByRole("button", { name: "target" });
targetButton.focus();
await waitFor(() => {
expect(screen.getByText("content")).to.exist;
expect(targetButton).to.equal(document.activeElement);
});
});
it("does not open popover on target focus when interactionKind is CLICK", async () => {
render(
,
);
const targetButton = screen.getByRole("button", { name: "target" });
targetButton.focus();
expect(screen.queryByText("content")).not.toBeInTheDocument();
expect(targetButton).to.equal(document.activeElement);
});
it("does not open popover on target focus when interactionKind is CLICK_TARGET_ONLY", () => {
render(
,
);
const targetButton = screen.getByRole("button", { name: "target" });
targetButton.focus();
expect(screen.queryByText("content")).not.toBeInTheDocument();
expect(targetButton).to.equal(document.activeElement);
});
});
describe("if false", () => {
it("does not add tabindex to target's child node when interactionKind is HOVER", () => {
render(
,
);
expect(screen.getByRole("button", { name: "target" }).getAttribute("tabindex")).not.toBeInTheDocument();
});
it("should not add `tabindex` to target's child node when interactionKind is `HOVER_TARGET_ONLY`", () => {
render(
,
);
expect(screen.getByRole("button", { name: "target" }).getAttribute("tabindex")).not.toBeInTheDocument();
});
it("does not add tabindex to target's child node when interactionKind is CLICK", () => {
render(
,
);
expect(screen.getByRole("button", { name: "target" }).getAttribute("tabindex")).not.toBeInTheDocument();
});
it("does not add tabindex to target's child node when interactionKind is CLICK_TARGET_ONLY", () => {
render(
,
);
expect(screen.getByRole("button", { name: "target" }).getAttribute("tabindex")).not.toBeInTheDocument();
});
it("does not open popover on target focus when interactionKind is HOVER", () => {
render(
,
);
const targetButton = screen.getByRole("button", { name: "target" });
targetButton.focus();
expect(screen.queryByText("content")).not.toBeInTheDocument();
expect(targetButton).to.equal(document.activeElement);
});
it("does not open popover on target focus when interactionKind is HOVER_TARGET_ONLY", () => {
render(
,
);
const targetButton = screen.getByRole("button", { name: "target" });
targetButton.focus();
expect(screen.queryByText("content")).not.toBeInTheDocument();
expect(targetButton).to.equal(document.activeElement);
});
it("does not open popover on target focus when interactionKind is CLICK", () => {
render(
,
);
const targetButton = screen.getByRole("button", { name: "target" });
targetButton.focus();
expect(screen.queryByText("content")).not.toBeInTheDocument();
expect(targetButton).to.equal(document.activeElement);
});
it("does not open popover on target focus when interactionKind is CLICK_TARGET_ONLY", () => {
render(
,
);
const targetButton = screen.getByRole("button", { name: "target" });
targetButton.focus();
expect(screen.queryByText("content")).not.toBeInTheDocument();
expect(targetButton).to.equal(document.activeElement);
});
});
});
describe("in controlled mode", () => {
it("state respects isOpen prop", () => {
const { rerender } = render(
,
);
expect(screen.queryByText("content")).not.toBeInTheDocument();
rerender(
,
);
expect(screen.getByText("content")).to.exist;
});
it("state does not update on user (click) interaction", async () => {
const user = userEvent.setup();
render(
,
);
await user.click(screen.getByRole("button", { name: "target" }));
expect(screen.queryByText("content")).not.toBeInTheDocument();
});
it("state does not update on user (key) interaction", async () => {
const user = userEvent.setup();
render(
,
);
expect(screen.getByText("content")).to.exist;
await user.keyboard("{Escape}");
expect(screen.getByText("content")).to.exist;
});
describe("disabled=true takes precedence over isOpen=true", () => {
it("on mount", () => {
render(
,
);
expect(screen.queryByText("content")).not.toBeInTheDocument();
});
it("onInteraction not called if changing from closed to open (b/c popover is still closed)", () => {
const onInteraction = vi.fn();
const { rerender } = render(
,
);
expect(onInteraction).not.toHaveBeenCalled();
rerender(
,
);
expect(screen.queryByText("content")).not.toBeInTheDocument();
expect(onInteraction).not.toHaveBeenCalled();
});
it("onInteraction not called if changing from open to closed (b/c popover was already closed)", () => {
const onInteraction = vi.fn();
const { rerender } = render(
,
);
expect(onInteraction).not.toHaveBeenCalled();
rerender(
,
);
expect(screen.queryByText("content")).not.toBeInTheDocument();
expect(onInteraction).not.toHaveBeenCalled();
});
it("onInteraction called if open and changing to disabled (b/c popover will close)", async () => {
const onInteraction = vi.fn();
const { rerender } = render(
,
);
await waitFor(() => expect(screen.getByText("content")).to.exist);
expect(onInteraction).not.toHaveBeenCalled();
rerender(
,
);
await waitFor(() => expect(screen.queryByText("content")).not.toBeInTheDocument());
expect(onInteraction).toHaveBeenCalled();
});
it("onInteraction called if open and changing to not-disabled (b/c popover will open)", async () => {
const onInteraction = vi.fn();
const { rerender } = render(
,
);
expect(onInteraction).not.toHaveBeenCalled();
rerender(
,
);
await waitFor(() => expect(screen.getByText("content")).to.exist);
expect(onInteraction).toHaveBeenCalled();
});
});
it("onClose is invoked with event when popover would close", async () => {
const user = userEvent.setup();
const onClose = vi.fn();
render(
close}
isOpen={true}
onClose={onClose}
>
,
);
const closeButton = screen.getByRole("button", { name: "close" });
await waitFor(() => expect(closeButton).to.exist);
await user.click(screen.getByRole("button", { name: "close" }));
expect(onClose).toHaveBeenCalledOnce();
expect(onClose.mock.calls[0][0]).toBeDefined();
});
describe("onInteraction()", () => {
it("is invoked with `true` when closed popover target is clicked", async () => {
const user = userEvent.setup();
const onInteraction = vi.fn();
render(
,
);
await user.click(screen.getByRole("button", { name: "target" }));
expect(onInteraction).toHaveBeenCalledOnce();
expect(onInteraction).toHaveBeenCalledWith(true, expect.anything());
});
it("is invoked with `false` when open popover target is clicked", async () => {
const user = userEvent.setup();
const onInteraction = vi.fn();
const { container } = render(
,
);
const target = container.querySelector(`.${Classes.POPOVER_TARGET}`);
expect(target).to.exist;
await user.click(target!);
expect(onInteraction).toHaveBeenCalledOnce();
expect(onInteraction).toHaveBeenCalledWith(false, expect.anything());
});
it("is invoked with `false` when open modal popover backdrop is clicked", async () => {
const user = userEvent.setup();
const onInteraction = vi.fn();
render(
,
);
const backdrop = screen.getByTestId("test-backdrop");
await user.click(backdrop);
expect(onInteraction).toHaveBeenCalledOnce();
expect(onInteraction).toHaveBeenCalledWith(false, expect.anything());
});
it("is invoked with `false` when clicking POPOVER_DISMISS", async () => {
const user = userEvent.setup();
const onInteraction = vi.fn();
render(
close}
isOpen={true}
onInteraction={onInteraction}
>
,
);
const closeButton = screen.getByRole("button", { name: "close" });
await waitFor(() => expect(closeButton).to.exist);
await user.click(closeButton);
expect(onInteraction).toHaveBeenCalledOnce();
expect(onInteraction).toHaveBeenCalledWith(false, expect.anything());
});
it("is invoked with `false` when the document is mousedowned", async () => {
const user = userEvent.setup();
const onInteraction = vi.fn();
render(
,
);
await user.click(document.documentElement);
expect(onInteraction).toHaveBeenCalledOnce();
expect(onInteraction).toHaveBeenCalledWith(false, expect.anything());
});
});
it("does not apply active class to target when open", () => {
render(
,
);
const targetButton = screen.getByRole("button", { name: "target" });
expect(targetButton).not.toHaveClass(Classes.ACTIVE);
});
});
describe("in uncontrolled mode", () => {
it("setting defaultIsOpen=true renders open popover", async () => {
render(
,
);
await waitFor(() => expect(screen.getByText("content")).to.exist);
});
it("with defaultIsOpen=true, popover can still be closed", async () => {
const user = userEvent.setup();
render(
close} defaultIsOpen={true}>
,
);
await waitFor(() => expect(screen.getByRole("button", { name: "close" })).to.exist);
await user.click(screen.getByRole("button", { name: "close" }));
await waitFor(() => expect(screen.queryByRole("button", { name: "close" })).not.toBeInTheDocument());
});
it("CLICK_TARGET_ONLY works properly", async () => {
const user = userEvent.setup();
const { container } = render(
,
);
const target = container.querySelector(`.${Classes.POPOVER_TARGET}`);
expect(target).to.exist;
await user.click(target!);
await waitFor(() => expect(screen.getByText("content")).to.exist);
await user.click(target!);
await waitFor(() => expect(screen.queryByText("content")).not.toBeInTheDocument());
});
it("HOVER_TARGET_ONLY works properly", async () => {
const user = userEvent.setup();
const { container } = render(
,
);
const target = container.querySelector(`.${Classes.POPOVER_TARGET}`);
expect(target).to.exist;
await user.hover(target!);
await waitFor(() => expect(screen.getByText("content")).to.exist);
await user.unhover(target!);
await waitFor(() => expect(screen.queryByText("content")).not.toBeInTheDocument());
});
it("inline HOVER_TARGET_ONLY works properly when openOnTargetFocus={false}", async () => {
const user = userEvent.setup();
const { container } = render(
,
);
const target = container.querySelector(`.${Classes.POPOVER_TARGET}`);
expect(target).to.exist;
await user.hover(target!);
await waitFor(() => expect(screen.getByText("content")).to.exist);
await user.unhover(target!);
await waitFor(() => expect(screen.queryByText("content")).not.toBeInTheDocument());
});
it("inline HOVER works properly", async () => {
const user = userEvent.setup();
const { container } = render(
,
);
const target = container.querySelector(`.${Classes.POPOVER_TARGET}`);
expect(target).to.exist;
await user.hover(target!);
await waitFor(() => expect(screen.getByText("content")).to.exist);
await user.unhover(target!);
await waitFor(() => expect(screen.queryByText("content")).not.toBeInTheDocument());
});
it("clicking POPOVER_DISMISS closes popover when usePortal=true", async () => {
const user = userEvent.setup();
render(
close}
defaultIsOpen={true}
usePortal={true}
>
,
);
await waitFor(() => expect(screen.getByRole("button", { name: "close" })).to.exist);
await user.click(screen.getByRole("button", { name: "close" }));
await waitFor(() => expect(screen.queryByRole("button", { name: "close" })).not.toBeInTheDocument());
});
it("clicking POPOVER_DISMISS closes popover when usePortal=false", async () => {
const user = userEvent.setup();
render(
close}
defaultIsOpen={true}
usePortal={false}
>
,
);
await waitFor(() => expect(screen.getByRole("button", { name: "close" })).to.exist);
await user.click(screen.getByRole("button", { name: "close" }));
await waitFor(() => expect(screen.queryByRole("button", { name: "close" })).not.toBeInTheDocument());
});
it.skip("pressing Escape closes popover when canEscapeKeyClose=true and usePortal=false", async () => {
const user = userEvent.setup();
render(
,
);
await user.click(screen.getByRole("button", { name: "target" }));
await waitFor(() => expect(screen.getByText("content")).to.exist);
await user.keyboard("{Escape}");
await waitFor(() => expect(screen.queryByText("content")).not.toBeInTheDocument());
});
it("setting disabled=true prevents opening popover", async () => {
const user = userEvent.setup();
render(
,
);
await user.click(screen.getByRole("button", { name: "target" }));
expect(screen.queryByText("content")).not.toBeInTheDocument();
});
it("setting disabled=true hides open popover", async () => {
const user = userEvent.setup();
const { rerender } = render(
,
);
await user.click(screen.getByRole("button", { name: "target" }));
await waitFor(() => expect(screen.getByText("content")).to.exist);
rerender(
,
);
await waitFor(() => expect(screen.queryByText("content")).not.toBeInTheDocument());
});
it("console.warns if onInteraction is set", () => {
const warnSpy = vi.spyOn(console, "warn").mockImplementation(vi.fn());
render(
false}>
,
);
expect(warnSpy.mock.calls[0][0]).toBe(Errors.POPOVER_WARN_UNCONTROLLED_ONINTERACTION);
warnSpy.mockRestore();
});
it("does apply active class to target when open", async () => {
const user = userEvent.setup();
render(
,
);
await user.click(screen.getByRole("button", { name: "target" }));
expect(screen.getByRole("button", { name: "target" })).toHaveClass(Classes.ACTIVE);
});
});
describe("when composed with ", () => {
it("shows tooltip on hover", async () => {
const user = userEvent.setup();
render(
,
);
const targetButton = screen.getByRole("button", { name: "target" });
await user.hover(targetButton);
await waitFor(() => expect(screen.getByText("tooltip content")).to.exist);
});
it("shows popover on click", async () => {
const user = userEvent.setup();
render(
,
);
const targetButton = screen.getByRole("button", { name: "target" });
await user.click(targetButton);
await waitFor(() => expect(screen.getByText("popover content")).to.exist);
expect(screen.queryByText("tooltip content")).not.toBeInTheDocument();
});
it("the target is focusable", () => {
render(
,
);
const targetButton = screen.getByRole("button", { name: "target" });
expect(targetButton.getAttribute("tabindex")).to.equal("0");
});
describe("when disabled=true", () => {
it("shows tooltip on hover", async () => {
const user = userEvent.setup();
render(
,
);
const targetButton = screen.getByRole("button", { name: "target" });
await user.hover(targetButton);
await waitFor(() => expect(screen.getByText("tooltip content")).to.exist);
});
it("does not show popover on click", async () => {
const user = userEvent.setup();
render(
,
);
const targetButton = screen.getByRole("button", { name: "target" });
await user.click(targetButton);
expect(screen.queryByText("popover content")).not.toBeInTheDocument();
});
it("the target is focusable", () => {
render(
,
);
const targetButton = screen.getByRole("button", { name: "target" });
expect(targetButton.getAttribute("tabindex")).to.equal("0");
});
});
});
describe("when composed with a disabled ", () => {
it("does not show tooltip on hover", async () => {
const user = userEvent.setup();
render(
,
);
const targetButton = screen.getByRole("button", { name: "target" });
await user.hover(targetButton);
expect(screen.queryByText("tooltip content")).not.toBeInTheDocument();
});
it("shows popover on click", async () => {
const user = userEvent.setup();
render(
,
);
const targetButton = screen.getByRole("button", { name: "target" });
await user.click(targetButton);
await waitFor(() => expect(screen.getByText("popover content")).to.exist);
});
it("the target is not focusable", () => {
render(
,
);
const targetButton = screen.getByRole("button", { name: "target" });
expect(targetButton.getAttribute("tabindex")).not.toBeInTheDocument();
});
describe("when disabled=true", () => {
it("does not show tooltip on hover", async () => {
const user = userEvent.setup();
render(
,
);
const targetButton = screen.getByRole("button", { name: "target" });
await user.hover(targetButton);
expect(screen.queryByText("tooltip content")).not.toBeInTheDocument();
});
it("does not show popover on click", async () => {
const user = userEvent.setup();
render(
,
);
const targetButton = screen.getByRole("button", { name: "target" });
await user.click(targetButton);
expect(screen.queryByText("popover content")).not.toBeInTheDocument();
});
it("the target is not focusable", () => {
render(
,
);
const targetButton = screen.getByRole("button", { name: "target" });
expect(targetButton.getAttribute("tabindex")).not.toBeInTheDocument();
});
});
});
describe("Popper.js integration", () => {
it("renders arrow element by default", async () => {
const { baseElement } = render(
,
);
await waitFor(() => expect(baseElement.querySelector(`.${Classes.POPOVER_ARROW}`)).to.exist);
});
it("arrow can be disabled via modifiers", async () => {
const { baseElement } = render(
,
);
expect(baseElement.querySelector(`.${Classes.POPOVER_ARROW}`)).not.toBeInTheDocument();
});
it("arrow can be disabled via minimal prop", () => {
const { baseElement } = render(
,
);
expect(baseElement.querySelector(`.${Classes.POPOVER_ARROW}`)).not.toBeInTheDocument();
});
it("matches target width via custom modifier", () => {
const { container } = render(
,
);
const targetElement = screen.getByRole("button", { name: "target" });
const popoverElement = container.querySelector(`.${Classes.POPOVER}`);
expect(popoverElement).to.exist;
expect(popoverElement?.clientWidth, "content width should equal target width +/- 5px").toBeCloseTo(
targetElement.clientWidth,
0,
);
});
});
describe("closing on click", () => {
it("Classes.POPOVER_DISMISS closes on click", async () => {
const user = userEvent.setup();
render(
dismiss} defaultIsOpen={true}>
,
);
await waitFor(() => expect(screen.getByRole("button", { name: "dismiss" })).to.exist);
await user.click(screen.getByRole("button", { name: "dismiss" }));
await waitFor(() => expect(screen.queryByRole("button", { name: "dismiss" })).not.toBeInTheDocument());
});
it("Classes.POPOVER_DISMISS_OVERRIDE does not close", async () => {
const user = userEvent.setup();
render(
}
defaultIsOpen={true}
>
,
);
expect(screen.getByRole("button", { name: "dismiss" })).to.exist;
await user.click(screen.getByRole("button", { name: "dismiss" }));
expect(screen.getByRole("button", { name: "dismiss" })).to.exist;
});
it(":disabled does not close", async () => {
const user = userEvent.setup();
render(
}
defaultIsOpen={true}
>
,
);
expect(screen.getByRole("button", { name: "dismiss" })).to.exist;
await user.click(screen.getByRole("button", { name: "dismiss" }));
expect(screen.getByRole("button", { name: "dismiss" })).to.exist;
});
it("Classes.DISABLED does not close", async () => {
const user = userEvent.setup();
render(
}
defaultIsOpen={true}
>
,
);
expect(screen.getByRole("button", { name: "dismiss" })).to.exist;
await user.click(screen.getByRole("button", { name: "dismiss" }));
expect(screen.getByRole("button", { name: "dismiss" })).to.exist;
});
it("captureDismiss={true} inner dismiss does not close outer popover", async () => {
const user = userEvent.setup();
render(
}
>
}
>
,
);
expect(screen.getByRole("button", { name: "dismiss" })).to.exist;
await user.click(screen.getByRole("button", { name: "dismiss" }));
await waitFor(() => expect(screen.queryByRole("button", { name: "dismiss" })).not.toBeInTheDocument());
expect(screen.getByRole("button", { name: "inner target" })).to.exist;
});
it("captureDismiss={false} inner dismiss closes outer popover", async () => {
const user = userEvent.setup();
render(
}
>
}
>
,
);
expect(screen.getByRole("button", { name: "dismiss" })).to.exist;
await user.click(screen.getByRole("button", { name: "dismiss" }));
await waitFor(() => expect(screen.queryByRole("button", { name: "dismiss" })).not.toBeInTheDocument());
await waitFor(() => expect(screen.queryByRole("button", { name: "inner target" })).not.toBeInTheDocument());
});
});
describe("key interactions on Button target", () => {
describe.skip("Space key down opens click interaction popover", () => {
it("when autoFocus={true}", async () => {
const user = userEvent.setup();
const { container } = render(
,
);
const targetButton = screen.getByRole("button", { name: "target" });
targetButton.focus();
await user.keyboard("{space}");
await waitFor(() => expect(screen.getByText("content")).to.exist);
const overlay = container.querySelector(`.${Classes.OVERLAY}`);
expect(overlay?.contains(document.activeElement)).to.be.true;
});
it("when autoFocus={false}", async () => {
const user = userEvent.setup();
render(
,
);
const targetButton = screen.getByRole("button", { name: "target" });
targetButton.focus();
await user.keyboard("{space}");
await waitFor(() => expect(screen.getByText("content")).to.exist);
expect(targetButton).to.equal(document.activeElement);
});
});
});
describe("compatibility", () => {
it("renderTarget type definition allows sending props to child components", () => {
render(
(
)}
/>,
);
});
});
});