mirror of
https://github.com/SigNoz/signoz.git
synced 2026-04-16 17:00:28 +01:00
Compare commits
2 Commits
feat/alert
...
chore/tool
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
159f54e79c | ||
|
|
b2ba02ccbf |
@@ -25,6 +25,7 @@ export default function ChartWrapper({
|
||||
showTooltip = true,
|
||||
showLegend = true,
|
||||
canPinTooltip = false,
|
||||
pinKey,
|
||||
syncMode,
|
||||
syncKey,
|
||||
onDestroy = noop,
|
||||
@@ -93,6 +94,7 @@ export default function ChartWrapper({
|
||||
<TooltipPlugin
|
||||
config={config}
|
||||
canPinTooltip={canPinTooltip}
|
||||
pinKey={pinKey}
|
||||
syncMode={syncMode}
|
||||
maxWidth={Math.max(
|
||||
TOOLTIP_MIN_WIDTH,
|
||||
|
||||
@@ -14,6 +14,8 @@ interface BaseChartProps {
|
||||
showLegend?: boolean;
|
||||
timezone?: Timezone;
|
||||
canPinTooltip?: boolean;
|
||||
/** Key that pins the tooltip while hovering. Defaults to DEFAULT_PIN_TOOLTIP_KEY ('l'). */
|
||||
pinKey?: string;
|
||||
yAxisUnit?: string;
|
||||
decimalPrecision?: PrecisionOption;
|
||||
pinnedTooltipElement?: (clickData: TooltipClickData) => React.ReactNode;
|
||||
|
||||
@@ -121,6 +121,7 @@ function BarPanel(props: PanelWrapperProps): JSX.Element {
|
||||
legendConfig={{
|
||||
position: widget?.legendPosition ?? LegendPosition.BOTTOM,
|
||||
}}
|
||||
canPinTooltip
|
||||
plotRef={onPlotRef}
|
||||
onDestroy={onPlotDestroy}
|
||||
yAxisUnit={widget.yAxisUnit}
|
||||
|
||||
@@ -112,6 +112,7 @@ function TimeSeriesPanel(props: PanelWrapperProps): JSX.Element {
|
||||
legendConfig={{
|
||||
position: widget?.legendPosition ?? LegendPosition.BOTTOM,
|
||||
}}
|
||||
canPinTooltip
|
||||
yAxisUnit={widget.yAxisUnit}
|
||||
decimalPrecision={widget.decimalPrecision}
|
||||
timezone={timezone}
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
} from './tooltipController';
|
||||
import {
|
||||
DashboardCursorSync,
|
||||
TooltipClickData,
|
||||
TooltipControllerContext,
|
||||
TooltipControllerState,
|
||||
TooltipLayoutInfo,
|
||||
@@ -31,6 +30,8 @@ const INTERACTIVE_CONTAINER_CLASSNAME = '.tooltip-plugin-container';
|
||||
// Delay before hiding an unpinned tooltip when the cursor briefly leaves
|
||||
// the plot – this avoids flicker when moving between nearby points.
|
||||
const HOVER_DISMISS_DELAY_MS = 100;
|
||||
// Default key that pins the tooltip while hovering over the chart.
|
||||
export const DEFAULT_PIN_TOOLTIP_KEY = 'l';
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
export default function TooltipPlugin({
|
||||
@@ -42,6 +43,7 @@ export default function TooltipPlugin({
|
||||
syncKey = '_tooltip_sync_global_',
|
||||
pinnedTooltipElement,
|
||||
canPinTooltip = false,
|
||||
pinKey = DEFAULT_PIN_TOOLTIP_KEY,
|
||||
}: TooltipPluginProps): JSX.Element | null {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const rafId = useRef<number | null>(null);
|
||||
@@ -259,68 +261,73 @@ export default function TooltipPlugin({
|
||||
}
|
||||
};
|
||||
|
||||
// When pinning is enabled, a click on the plot overlay while
|
||||
// hovering converts the transient tooltip into a pinned one.
|
||||
// Uses getPlot(controller) to avoid closing over u (plot), which
|
||||
// would retain the plot and detached canvases across unmounts.
|
||||
const handleUPlotOverClick = (event: MouseEvent): void => {
|
||||
// Pin the tooltip when the user presses the configured pin key while
|
||||
// hovering over the chart. Uses getFocusedSeriesAtPosition with a
|
||||
// synthetic event built from the current uPlot cursor position so the
|
||||
// same series-resolution logic as click-pinning is reused.
|
||||
const handleKeyDown = (event: KeyboardEvent): void => {
|
||||
if (event.key.toLowerCase() !== pinKey.toLowerCase()) {
|
||||
return;
|
||||
}
|
||||
const plot = getPlot(controller);
|
||||
if (
|
||||
plot &&
|
||||
event.target === plot.over &&
|
||||
controller.hoverActive &&
|
||||
!controller.pinned &&
|
||||
controller.focusedSeriesIndex != null
|
||||
!plot ||
|
||||
!controller.hoverActive ||
|
||||
controller.pinned ||
|
||||
controller.focusedSeriesIndex == null
|
||||
) {
|
||||
const xValue = plot.posToVal(event.offsetX, 'x');
|
||||
const yValue = plot.posToVal(event.offsetY, 'y');
|
||||
const focusedSeries = getFocusedSeriesAtPosition(event, plot);
|
||||
|
||||
let clickedDataTimestamp = xValue;
|
||||
if (focusedSeries) {
|
||||
const dataIndex = plot.posToIdx(event.offsetX);
|
||||
const xSeriesData = plot.data[0];
|
||||
if (
|
||||
xSeriesData &&
|
||||
dataIndex >= 0 &&
|
||||
dataIndex < xSeriesData.length &&
|
||||
xSeriesData[dataIndex] !== undefined
|
||||
) {
|
||||
clickedDataTimestamp = xSeriesData[dataIndex];
|
||||
}
|
||||
}
|
||||
|
||||
const clickData: TooltipClickData = {
|
||||
xValue,
|
||||
yValue,
|
||||
focusedSeries,
|
||||
clickedDataTimestamp,
|
||||
mouseX: event.offsetX,
|
||||
mouseY: event.offsetY,
|
||||
absoluteMouseX: event.clientX,
|
||||
absoluteMouseY: event.clientY,
|
||||
};
|
||||
|
||||
controller.clickData = clickData;
|
||||
|
||||
setTimeout(() => {
|
||||
controller.pinned = true;
|
||||
scheduleRender(true);
|
||||
}, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const cursorLeft = plot.cursor.left ?? -1;
|
||||
const cursorTop = plot.cursor.top ?? -1;
|
||||
if (cursorLeft < 0 || cursorTop < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const plotRect = plot.over.getBoundingClientRect();
|
||||
const syntheticEvent = ({
|
||||
clientX: plotRect.left + cursorLeft,
|
||||
clientY: plotRect.top + cursorTop,
|
||||
target: plot.over,
|
||||
offsetX: cursorLeft,
|
||||
offsetY: cursorTop,
|
||||
} as unknown) as MouseEvent;
|
||||
|
||||
const xValue = plot.posToVal(cursorLeft, 'x');
|
||||
const yValue = plot.posToVal(cursorTop, 'y');
|
||||
const focusedSeries = getFocusedSeriesAtPosition(syntheticEvent, plot);
|
||||
|
||||
const dataIndex = plot.posToIdx(cursorLeft);
|
||||
let clickedDataTimestamp = xValue;
|
||||
const xSeriesData = plot.data[0];
|
||||
if (
|
||||
xSeriesData &&
|
||||
dataIndex >= 0 &&
|
||||
dataIndex < xSeriesData.length &&
|
||||
xSeriesData[dataIndex] !== undefined
|
||||
) {
|
||||
clickedDataTimestamp = xSeriesData[dataIndex];
|
||||
}
|
||||
|
||||
controller.clickData = {
|
||||
xValue,
|
||||
yValue,
|
||||
focusedSeries,
|
||||
clickedDataTimestamp,
|
||||
mouseX: cursorLeft,
|
||||
mouseY: cursorTop,
|
||||
absoluteMouseX: syntheticEvent.clientX,
|
||||
absoluteMouseY: syntheticEvent.clientY,
|
||||
};
|
||||
controller.pinned = true;
|
||||
scheduleRender(true);
|
||||
};
|
||||
|
||||
let overClickHandler: ((event: MouseEvent) => void) | null = null;
|
||||
|
||||
// Called once per uPlot instance; used to store the instance
|
||||
// on the controller and optionally attach the pinning handler.
|
||||
// Called once per uPlot instance; used to store the instance on the controller.
|
||||
const handleInit = (u: uPlot): void => {
|
||||
controller.plot = u;
|
||||
updateState({ hasPlot: true });
|
||||
if (canPinTooltip) {
|
||||
overClickHandler = handleUPlotOverClick;
|
||||
u.over.addEventListener('click', overClickHandler);
|
||||
}
|
||||
};
|
||||
|
||||
// If the underlying data changes we drop any pinned tooltip,
|
||||
@@ -365,6 +372,9 @@ export default function TooltipPlugin({
|
||||
|
||||
window.addEventListener('resize', handleWindowResize);
|
||||
window.addEventListener('scroll', handleScroll, true);
|
||||
if (canPinTooltip) {
|
||||
document.addEventListener('keydown', handleKeyDown, true);
|
||||
}
|
||||
|
||||
return (): void => {
|
||||
layoutRef.current?.observer.disconnect();
|
||||
@@ -372,6 +382,9 @@ export default function TooltipPlugin({
|
||||
window.removeEventListener('scroll', handleScroll, true);
|
||||
document.removeEventListener('mousedown', onOutsideInteraction, true);
|
||||
document.removeEventListener('keydown', onOutsideInteraction, true);
|
||||
if (canPinTooltip) {
|
||||
document.removeEventListener('keydown', handleKeyDown, true);
|
||||
}
|
||||
cancelPendingRender();
|
||||
removeReadyHook();
|
||||
removeInitHook();
|
||||
@@ -379,13 +392,6 @@ export default function TooltipPlugin({
|
||||
removeSetSeriesHook();
|
||||
removeSetLegendHook();
|
||||
removeSetCursorHook();
|
||||
if (overClickHandler) {
|
||||
const plot = getPlot(controller);
|
||||
if (plot) {
|
||||
plot.over.removeEventListener('click', overClickHandler);
|
||||
}
|
||||
overClickHandler = null;
|
||||
}
|
||||
clearPlotReferences();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -423,8 +429,12 @@ export default function TooltipPlugin({
|
||||
}, [isHovering, hasPlot]);
|
||||
|
||||
const tooltipBody = useMemo(() => {
|
||||
if (isPinned && pinnedTooltipElement != null && viewState.clickData != null) {
|
||||
return pinnedTooltipElement(viewState.clickData);
|
||||
if (isPinned) {
|
||||
if (pinnedTooltipElement != null && viewState.clickData != null) {
|
||||
return pinnedTooltipElement(viewState.clickData);
|
||||
}
|
||||
// No custom pinned element — keep showing the last hover contents.
|
||||
return contents ?? null;
|
||||
}
|
||||
|
||||
if (isHovering) {
|
||||
|
||||
@@ -102,6 +102,12 @@ export function updateHoverState(
|
||||
controller: TooltipControllerState,
|
||||
syncTooltipWithDashboard: boolean,
|
||||
): void {
|
||||
// When pinned, keep hoverActive stable so the tooltip stays visible
|
||||
// until explicitly dismissed — the cursor lock fires asynchronously
|
||||
// and setSeries/setLegend can otherwise race and clear hoverActive.
|
||||
if (controller.pinned) {
|
||||
return;
|
||||
}
|
||||
// When the cursor is driven by dashboard‑level sync, we only show
|
||||
// the tooltip if the plot is in viewport and at least one series
|
||||
// is active. Otherwise we fall back to local interaction logic.
|
||||
|
||||
@@ -37,6 +37,8 @@ export interface TooltipLayoutInfo {
|
||||
export interface TooltipPluginProps {
|
||||
config: UPlotConfigBuilder;
|
||||
canPinTooltip?: boolean;
|
||||
/** Key that pins the tooltip while hovering. Defaults to DEFAULT_PIN_TOOLTIP_KEY ('l'). */
|
||||
pinKey?: string;
|
||||
syncMode?: DashboardCursorSync;
|
||||
syncKey?: string;
|
||||
render: (args: TooltipRenderArgs) => ReactNode;
|
||||
|
||||
@@ -6,7 +6,9 @@ import type uPlot from 'uplot';
|
||||
|
||||
import { TooltipRenderArgs } from '../../components/types';
|
||||
import { UPlotConfigBuilder } from '../../config/UPlotConfigBuilder';
|
||||
import TooltipPlugin from '../TooltipPlugin/TooltipPlugin';
|
||||
import TooltipPlugin, {
|
||||
DEFAULT_PIN_TOOLTIP_KEY,
|
||||
} from '../TooltipPlugin/TooltipPlugin';
|
||||
import { DashboardCursorSync } from '../TooltipPlugin/types';
|
||||
|
||||
// Avoid depending on the full uPlot + onClickPlugin behaviour in these tests.
|
||||
@@ -60,7 +62,7 @@ function getHandler(config: ConfigMock, hookName: string): HookHandler {
|
||||
function createFakePlot(): {
|
||||
over: HTMLDivElement;
|
||||
setCursor: jest.Mock<void, [uPlot.Cursor]>;
|
||||
cursor: { event: Record<string, unknown> };
|
||||
cursor: { event: Record<string, unknown>; left: number; top: number };
|
||||
posToVal: jest.Mock<number, [value: number]>;
|
||||
posToIdx: jest.Mock<number, []>;
|
||||
data: [number[], number[]];
|
||||
@@ -71,7 +73,9 @@ function createFakePlot(): {
|
||||
return {
|
||||
over,
|
||||
setCursor: jest.fn(),
|
||||
cursor: { event: {} },
|
||||
// left / top are set to valid values so keyboard-pin tests do not
|
||||
// hit the "cursor off-screen" guard inside handleKeyDown.
|
||||
cursor: { event: {}, left: 50, top: 50 },
|
||||
// In real uPlot these map overlay coordinates to data-space values.
|
||||
posToVal: jest.fn((value: number) => value),
|
||||
posToIdx: jest.fn(() => 0),
|
||||
@@ -245,18 +249,21 @@ describe('TooltipPlugin', () => {
|
||||
// ---- Pin behaviour ----------------------------------------------------------
|
||||
|
||||
describe('pin behaviour', () => {
|
||||
it('pins the tooltip when canPinTooltip is true and overlay is clicked', () => {
|
||||
it('pins the tooltip when canPinTooltip is true and the pinKey is pressed while hovering', () => {
|
||||
const config = createConfigMock();
|
||||
|
||||
const fakePlot = renderAndActivateHover(config, undefined, {
|
||||
canPinTooltip: true,
|
||||
});
|
||||
renderAndActivateHover(config, undefined, { canPinTooltip: true });
|
||||
|
||||
const container = screen.getByTestId('tooltip-plugin-container');
|
||||
expect(container.classList.contains('pinned')).toBe(false);
|
||||
|
||||
act(() => {
|
||||
fakePlot.over.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||
document.body.dispatchEvent(
|
||||
new KeyboardEvent('keydown', {
|
||||
key: DEFAULT_PIN_TOOLTIP_KEY,
|
||||
bubbles: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
return waitFor(() => {
|
||||
@@ -272,7 +279,7 @@ describe('TooltipPlugin', () => {
|
||||
React.createElement('div', null, 'pinned-tooltip'),
|
||||
);
|
||||
|
||||
const fakePlot = renderAndActivateHover(
|
||||
renderAndActivateHover(
|
||||
config,
|
||||
() => React.createElement('div', null, 'hover-tooltip'),
|
||||
{
|
||||
@@ -284,7 +291,12 @@ describe('TooltipPlugin', () => {
|
||||
expect(screen.getByText('hover-tooltip')).toBeInTheDocument();
|
||||
|
||||
act(() => {
|
||||
fakePlot.over.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||
document.body.dispatchEvent(
|
||||
new KeyboardEvent('keydown', {
|
||||
key: DEFAULT_PIN_TOOLTIP_KEY,
|
||||
bubbles: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -318,9 +330,14 @@ describe('TooltipPlugin', () => {
|
||||
getHandler(config, 'setSeries')(fakePlot, 1, { focus: true });
|
||||
});
|
||||
|
||||
// Pin the tooltip.
|
||||
// Pin the tooltip via the keyboard shortcut.
|
||||
act(() => {
|
||||
fakePlot.over.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||
document.body.dispatchEvent(
|
||||
new KeyboardEvent('keydown', {
|
||||
key: DEFAULT_PIN_TOOLTIP_KEY,
|
||||
bubbles: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
// Wait until the tooltip is actually pinned (pointer events enabled)
|
||||
@@ -369,9 +386,14 @@ describe('TooltipPlugin', () => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
// Pin.
|
||||
// Pin via keyboard.
|
||||
act(() => {
|
||||
fakePlot.over.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||
document.body.dispatchEvent(
|
||||
new KeyboardEvent('keydown', {
|
||||
key: DEFAULT_PIN_TOOLTIP_KEY,
|
||||
bubbles: true,
|
||||
}),
|
||||
);
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
@@ -417,8 +439,14 @@ describe('TooltipPlugin', () => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
// Pin via keyboard.
|
||||
act(() => {
|
||||
fakePlot.over.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||
document.body.dispatchEvent(
|
||||
new KeyboardEvent('keydown', {
|
||||
key: DEFAULT_PIN_TOOLTIP_KEY,
|
||||
bubbles: true,
|
||||
}),
|
||||
);
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
@@ -467,8 +495,14 @@ describe('TooltipPlugin', () => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
// Pin via keyboard.
|
||||
act(() => {
|
||||
fakePlot.over.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||
document.body.dispatchEvent(
|
||||
new KeyboardEvent('keydown', {
|
||||
key: DEFAULT_PIN_TOOLTIP_KEY,
|
||||
bubbles: true,
|
||||
}),
|
||||
);
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
@@ -498,6 +532,170 @@ describe('TooltipPlugin', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ---- Keyboard pin edge cases ------------------------------------------------
|
||||
|
||||
describe('keyboard pin edge cases', () => {
|
||||
it('does not pin when cursor coordinates are negative (cursor off-screen)', () => {
|
||||
const config = createConfigMock();
|
||||
|
||||
render(
|
||||
React.createElement(TooltipPlugin, {
|
||||
config,
|
||||
render: () => React.createElement('div', null, 'tooltip-body'),
|
||||
syncMode: DashboardCursorSync.None,
|
||||
canPinTooltip: true,
|
||||
}),
|
||||
);
|
||||
|
||||
// Negative cursor coords — handleKeyDown bails out before pinning.
|
||||
const fakePlot = {
|
||||
...createFakePlot(),
|
||||
cursor: { event: {}, left: -1, top: -1 },
|
||||
};
|
||||
|
||||
act(() => {
|
||||
getHandler(config, 'init')(fakePlot);
|
||||
getHandler(config, 'setSeries')(fakePlot, 1, { focus: true });
|
||||
});
|
||||
|
||||
act(() => {
|
||||
document.body.dispatchEvent(
|
||||
new KeyboardEvent('keydown', {
|
||||
key: DEFAULT_PIN_TOOLTIP_KEY,
|
||||
bubbles: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
const container = screen.getByTestId('tooltip-plugin-container');
|
||||
expect(container.classList.contains('pinned')).toBe(false);
|
||||
});
|
||||
|
||||
it('does not pin when hover is not active', () => {
|
||||
const config = createConfigMock();
|
||||
|
||||
render(
|
||||
React.createElement(TooltipPlugin, {
|
||||
config,
|
||||
render: () => React.createElement('div', null, 'tooltip-body'),
|
||||
syncMode: DashboardCursorSync.None,
|
||||
canPinTooltip: true,
|
||||
}),
|
||||
);
|
||||
|
||||
const fakePlot = createFakePlot();
|
||||
|
||||
act(() => {
|
||||
// Initialise the plot but do NOT call setSeries – hoverActive stays false.
|
||||
getHandler(config, 'init')(fakePlot);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
document.body.dispatchEvent(
|
||||
new KeyboardEvent('keydown', {
|
||||
key: DEFAULT_PIN_TOOLTIP_KEY,
|
||||
bubbles: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
// The container exists once the plot is initialised, but it should
|
||||
// be hidden and not pinned since hover was never activated.
|
||||
const container = screen.getByTestId('tooltip-plugin-container');
|
||||
expect(container.classList.contains('pinned')).toBe(false);
|
||||
expect(container.classList.contains('visible')).toBe(false);
|
||||
});
|
||||
|
||||
it('ignores other keys and only pins on the configured pinKey', async () => {
|
||||
const config = createConfigMock();
|
||||
|
||||
renderAndActivateHover(config, undefined, {
|
||||
canPinTooltip: true,
|
||||
pinKey: 'p',
|
||||
});
|
||||
|
||||
// Default key 'l' should NOT pin when pinKey is 'p'.
|
||||
act(() => {
|
||||
document.body.dispatchEvent(
|
||||
new KeyboardEvent('keydown', {
|
||||
key: DEFAULT_PIN_TOOLTIP_KEY,
|
||||
bubbles: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen
|
||||
.getByTestId('tooltip-plugin-container')
|
||||
.classList.contains('pinned'),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
// Custom pin key 'p' SHOULD pin.
|
||||
act(() => {
|
||||
document.body.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'p', bubbles: true }),
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen
|
||||
.getByTestId('tooltip-plugin-container')
|
||||
.classList.contains('pinned'),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not register a keydown listener when canPinTooltip is false', () => {
|
||||
const config = createConfigMock();
|
||||
const addSpy = jest.spyOn(document, 'addEventListener');
|
||||
|
||||
render(
|
||||
React.createElement(TooltipPlugin, {
|
||||
config,
|
||||
render: () => null,
|
||||
syncMode: DashboardCursorSync.None,
|
||||
canPinTooltip: false,
|
||||
}),
|
||||
);
|
||||
|
||||
const keydownCalls = addSpy.mock.calls.filter(
|
||||
([type]) => type === 'keydown',
|
||||
);
|
||||
expect(keydownCalls).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('removes the keydown pin listener on unmount', () => {
|
||||
const config = createConfigMock();
|
||||
const addSpy = jest.spyOn(document, 'addEventListener');
|
||||
const removeSpy = jest.spyOn(document, 'removeEventListener');
|
||||
|
||||
const { unmount } = render(
|
||||
React.createElement(TooltipPlugin, {
|
||||
config,
|
||||
render: () => null,
|
||||
syncMode: DashboardCursorSync.None,
|
||||
canPinTooltip: true,
|
||||
}),
|
||||
);
|
||||
|
||||
const pinListenerCall = addSpy.mock.calls.find(
|
||||
([type]) => type === 'keydown',
|
||||
);
|
||||
expect(pinListenerCall).toBeDefined();
|
||||
if (!pinListenerCall) {
|
||||
return;
|
||||
}
|
||||
const [, pinListener, pinOptions] = pinListenerCall;
|
||||
|
||||
unmount();
|
||||
|
||||
expect(removeSpy).toHaveBeenCalledWith('keydown', pinListener, pinOptions);
|
||||
});
|
||||
});
|
||||
|
||||
// ---- Cursor sync ------------------------------------------------------------
|
||||
|
||||
describe('cursor sync', () => {
|
||||
|
||||
Reference in New Issue
Block a user