mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-14 16:40:30 +00:00
Split shared tooltip runtime owners
This commit is contained in:
parent
bc235e02b2
commit
e9643b3249
7 changed files with 332 additions and 105 deletions
|
|
@ -286,6 +286,14 @@ and `frontend-modern/src/components/shared/searchInputModel.ts` owns the shared
|
|||
search-input contract plus shortcut-hint and trailing-control policy. Future
|
||||
search-input work should extend those owners instead of pushing type-to-search
|
||||
or enhancement wiring back into the shared shell.
|
||||
The shared tooltip now follows that same owner split.
|
||||
`frontend-modern/src/components/shared/Tooltip.tsx` stays the render shell and
|
||||
singleton API boundary, `frontend-modern/src/components/shared/useTooltipState.ts`
|
||||
owns tooltip positioning lifecycle, RAF scheduling, and singleton visibility
|
||||
state, and `frontend-modern/src/components/shared/tooltipModel.ts` owns tooltip
|
||||
sanitization plus viewport-clamped positioning math. Future tooltip work should
|
||||
extend those owners instead of pushing singleton state, DOM measurement, or
|
||||
sanitization logic back into the shared shell.
|
||||
The shared collapsible search input now follows that same owner split.
|
||||
`frontend-modern/src/components/shared/CollapsibleSearchInput.tsx` stays the
|
||||
render shell, `frontend-modern/src/components/shared/useCollapsibleSearchInputState.ts`
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ import searchFieldSource from '@/components/shared/SearchField.tsx?raw';
|
|||
import searchFieldModelSource from '@/components/shared/searchFieldModel.ts?raw';
|
||||
import searchInputSource from '@/components/shared/SearchInput.tsx?raw';
|
||||
import searchInputModelSource from '@/components/shared/searchInputModel.ts?raw';
|
||||
import tooltipSource from '@/components/shared/Tooltip.tsx?raw';
|
||||
import tooltipModelSource from '@/components/shared/tooltipModel.ts?raw';
|
||||
import interactiveSparklineSource from '@/components/shared/InteractiveSparkline.tsx?raw';
|
||||
import interactiveSparklineModelSource from '@/components/shared/interactiveSparklineModel.ts?raw';
|
||||
import infrastructureSummaryTableSource from '@/components/shared/InfrastructureSummaryTable.tsx?raw';
|
||||
|
|
@ -49,6 +51,7 @@ import infrastructureSelectorStateSource from '@/components/shared/useInfrastruc
|
|||
import pulseDataGridStateSource from '@/components/shared/usePulseDataGridState.ts?raw';
|
||||
import searchFieldStateSource from '@/components/shared/useSearchFieldState.ts?raw';
|
||||
import searchInputStateSource from '@/components/shared/useSearchInputState.ts?raw';
|
||||
import tooltipStateSource from '@/components/shared/useTooltipState.ts?raw';
|
||||
import interactiveSparklineStateSource from '@/components/shared/useInteractiveSparklineState.ts?raw';
|
||||
import webInterfaceUrlFieldSource from '@/components/shared/WebInterfaceUrlField.tsx?raw';
|
||||
import webInterfaceUrlFieldModelSource from '@/components/shared/webInterfaceUrlFieldModel.ts?raw';
|
||||
|
|
@ -458,6 +461,28 @@ describe('shared primitive guardrails', () => {
|
|||
expect(searchInputModelSource).toContain('export interface SearchInputProps');
|
||||
});
|
||||
|
||||
it('keeps tooltip on shell, runtime, and model owners', () => {
|
||||
expect(tooltipSource).toContain('useTooltipState');
|
||||
expect(tooltipSource).toContain('createTooltipSystemState');
|
||||
expect(tooltipSource).not.toContain('createSignal');
|
||||
expect(tooltipSource).not.toContain('requestAnimationFrame');
|
||||
expect(tooltipSource).not.toContain('sanitizeTooltipContent');
|
||||
expect(tooltipSource).not.toContain('resolveTooltipPosition');
|
||||
|
||||
expect(tooltipStateSource).toContain('export function useTooltipState');
|
||||
expect(tooltipStateSource).toContain('export function createTooltipSystemState');
|
||||
expect(tooltipStateSource).toContain('createSignal');
|
||||
expect(tooltipStateSource).toContain('requestAnimationFrame');
|
||||
expect(tooltipStateSource).toContain('tooltipInstance');
|
||||
expect(tooltipStateSource).toContain('resolveTooltipPosition');
|
||||
expect(tooltipStateSource).toContain('sanitizeTooltipContent');
|
||||
|
||||
expect(tooltipModelSource).toContain('export function sanitizeTooltipContent');
|
||||
expect(tooltipModelSource).toContain('export function resolveTooltipPosition');
|
||||
expect(tooltipModelSource).toContain("export type TooltipAlignment = 'left' | 'center'");
|
||||
expect(tooltipModelSource).toContain("export type TooltipDirection = 'up' | 'down'");
|
||||
});
|
||||
|
||||
it('keeps collapsible search input on shell, runtime, and model owners', () => {
|
||||
expect(collapsibleSearchInputSource).toContain('useCollapsibleSearchInputState');
|
||||
expect(collapsibleSearchInputSource).not.toContain('createSignal');
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
import { Component, createSignal, createEffect, Show } from 'solid-js';
|
||||
import { Component, Show } from 'solid-js';
|
||||
import { Portal } from 'solid-js/web';
|
||||
import { createTooltipSystemState, useTooltipState } from './useTooltipState';
|
||||
import type { TooltipOptions } from './tooltipModel';
|
||||
|
||||
type TooltipAlignment = 'left' | 'center';
|
||||
type TooltipDirection = 'up' | 'down';
|
||||
|
||||
export interface TooltipOptions {
|
||||
align?: TooltipAlignment;
|
||||
direction?: TooltipDirection;
|
||||
maxWidth?: number;
|
||||
}
|
||||
export { hideTooltip, showTooltip } from './useTooltipState';
|
||||
export type { TooltipOptions } from './tooltipModel';
|
||||
|
||||
interface TooltipProps extends TooltipOptions {
|
||||
content: string;
|
||||
|
|
@ -17,125 +13,43 @@ interface TooltipProps extends TooltipOptions {
|
|||
visible: boolean;
|
||||
}
|
||||
|
||||
// Sanitize tooltip content to prevent XSS
|
||||
function sanitizeContent(content: string): string {
|
||||
// Remove any HTML tags and encode special characters
|
||||
return content
|
||||
.replace(/<[^>]*>/g, '') // Remove HTML tags
|
||||
.replace(/&/g, '&') // Encode ampersands
|
||||
.replace(/</g, '<') // Encode less than
|
||||
.replace(/>/g, '>') // Encode greater than
|
||||
.replace(/"/g, '"') // Encode quotes
|
||||
.replace(/'/g, '''); // Encode apostrophes
|
||||
}
|
||||
|
||||
const Tooltip: Component<TooltipProps> = (props) => {
|
||||
let tooltipRef: HTMLDivElement | undefined;
|
||||
const [position, setPosition] = createSignal({ left: 0, top: 0 });
|
||||
|
||||
createEffect(() => {
|
||||
if (!props.visible) {
|
||||
setPosition({ left: props.x, top: props.y });
|
||||
return;
|
||||
}
|
||||
|
||||
// Use requestAnimationFrame to ensure DOM is updated
|
||||
requestAnimationFrame(() => {
|
||||
if (!tooltipRef) return;
|
||||
|
||||
const rect = tooltipRef.getBoundingClientRect();
|
||||
const padding = 8;
|
||||
let left = props.x;
|
||||
let top = props.y;
|
||||
|
||||
const align = props.align ?? 'center';
|
||||
const direction = props.direction ?? 'up';
|
||||
|
||||
if (align === 'center') {
|
||||
left = props.x - rect.width / 2;
|
||||
}
|
||||
|
||||
if (direction === 'up') {
|
||||
top = props.y - rect.height - padding;
|
||||
} else {
|
||||
top = props.y + padding;
|
||||
}
|
||||
|
||||
// Clamp to viewport bounds with small offset to avoid touching edges
|
||||
const viewportPadding = 4;
|
||||
const maxLeft = window.innerWidth - rect.width - viewportPadding;
|
||||
const maxTop = window.innerHeight - rect.height - viewportPadding;
|
||||
|
||||
left = Math.min(Math.max(left, viewportPadding), Math.max(maxLeft, viewportPadding));
|
||||
top = Math.min(Math.max(top, viewportPadding), Math.max(maxTop, viewportPadding));
|
||||
|
||||
setPosition({ left, top });
|
||||
});
|
||||
});
|
||||
export const Tooltip: Component<TooltipProps> = (props) => {
|
||||
const state = useTooltipState(props);
|
||||
|
||||
return (
|
||||
<Show when={props.visible}>
|
||||
<Portal mount={document.body}>
|
||||
<div
|
||||
ref={tooltipRef}
|
||||
ref={state.setTooltipRef}
|
||||
class="fixed z-[9999] px-3 py-2 text-xs whitespace-pre-line rounded-md border shadow-sm pointer-events-none bg-surface text-base-content border-border leading-tight"
|
||||
style={{
|
||||
left: `${position().left}px`,
|
||||
top: `${position().top}px`,
|
||||
left: `${state.position().left}px`,
|
||||
top: `${state.position().top}px`,
|
||||
'max-width': `${props.maxWidth ?? 240}px`,
|
||||
opacity: props.visible ? '1' : '0',
|
||||
transition: 'opacity 120ms ease-out',
|
||||
}}
|
||||
textContent={sanitizeContent(props.content)}
|
||||
textContent={state.sanitizedContent()}
|
||||
/>
|
||||
</Portal>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
// Global tooltip singleton
|
||||
let tooltipInstance: {
|
||||
show: (content: string, x: number, y: number, options?: TooltipOptions) => void;
|
||||
hide: () => void;
|
||||
} | null = null;
|
||||
|
||||
export function createTooltipSystem() {
|
||||
const [visible, setVisible] = createSignal(false);
|
||||
const [content, setContent] = createSignal('');
|
||||
const [position, setPosition] = createSignal({ x: 0, y: 0 });
|
||||
const [options, setOptions] = createSignal<TooltipOptions>({});
|
||||
|
||||
tooltipInstance = {
|
||||
show: (content: string, x: number, y: number, opts?: TooltipOptions) => {
|
||||
setContent(content);
|
||||
setPosition({ x, y });
|
||||
setOptions(opts || {});
|
||||
setVisible(true);
|
||||
},
|
||||
hide: () => {
|
||||
setVisible(false);
|
||||
},
|
||||
};
|
||||
const state = createTooltipSystemState();
|
||||
|
||||
return () => (
|
||||
<Tooltip
|
||||
content={content()}
|
||||
x={position().x}
|
||||
y={position().y}
|
||||
visible={visible()}
|
||||
align={options().align}
|
||||
direction={options().direction}
|
||||
maxWidth={options().maxWidth}
|
||||
content={state.content()}
|
||||
x={state.position().x}
|
||||
y={state.position().y}
|
||||
visible={state.visible()}
|
||||
align={state.options().align}
|
||||
direction={state.options().direction}
|
||||
maxWidth={state.options().maxWidth}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function showTooltip(content: string, x: number, y: number, options?: TooltipOptions) {
|
||||
tooltipInstance?.show(content, x, y, options);
|
||||
}
|
||||
|
||||
export function hideTooltip() {
|
||||
tooltipInstance?.hide();
|
||||
}
|
||||
|
||||
export default Tooltip;
|
||||
|
|
|
|||
104
frontend-modern/src/components/shared/__tests__/Tooltip.test.tsx
Normal file
104
frontend-modern/src/components/shared/__tests__/Tooltip.test.tsx
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { cleanup, render, screen, waitFor } from '@solidjs/testing-library';
|
||||
import {
|
||||
Tooltip,
|
||||
createTooltipSystem,
|
||||
showTooltip,
|
||||
hideTooltip,
|
||||
} from '@/components/shared/Tooltip';
|
||||
import tooltipSource from '@/components/shared/Tooltip.tsx?raw';
|
||||
import tooltipModelSource from '@/components/shared/tooltipModel.ts?raw';
|
||||
import tooltipStateSource from '@/components/shared/useTooltipState.ts?raw';
|
||||
|
||||
describe('Tooltip', () => {
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('keeps tooltip on shell, runtime, and model owners', () => {
|
||||
expect(tooltipSource).toContain('useTooltipState');
|
||||
expect(tooltipSource).toContain('createTooltipSystemState');
|
||||
expect(tooltipSource).not.toContain('createSignal');
|
||||
expect(tooltipSource).not.toContain('requestAnimationFrame');
|
||||
expect(tooltipSource).not.toContain('sanitizeTooltipContent');
|
||||
expect(tooltipSource).not.toContain('resolveTooltipPosition');
|
||||
|
||||
expect(tooltipStateSource).toContain('export function useTooltipState');
|
||||
expect(tooltipStateSource).toContain('export function createTooltipSystemState');
|
||||
expect(tooltipStateSource).toContain('createSignal');
|
||||
expect(tooltipStateSource).toContain('requestAnimationFrame');
|
||||
expect(tooltipStateSource).toContain('tooltipInstance');
|
||||
expect(tooltipStateSource).toContain('resolveTooltipPosition');
|
||||
expect(tooltipStateSource).toContain('sanitizeTooltipContent');
|
||||
|
||||
expect(tooltipModelSource).toContain('export function sanitizeTooltipContent');
|
||||
expect(tooltipModelSource).toContain('export function resolveTooltipPosition');
|
||||
expect(tooltipModelSource).toContain("export type TooltipAlignment = 'left' | 'center'");
|
||||
expect(tooltipModelSource).toContain("export type TooltipDirection = 'up' | 'down'");
|
||||
});
|
||||
|
||||
it('sanitizes tooltip content through the model owner', async () => {
|
||||
render(() => <Tooltip content={`<b>"unsafe"</b> & 'quoted'`} x={24} y={24} visible />);
|
||||
|
||||
const tooltip = document.body.querySelector('div[style*="opacity: 1"]') as HTMLDivElement | null;
|
||||
expect(tooltip).not.toBeNull();
|
||||
if (!tooltip) return;
|
||||
|
||||
await waitFor(() => {
|
||||
expect(tooltip.textContent).toBe('"unsafe" & 'quoted'');
|
||||
expect(tooltip.innerHTML).not.toContain('<b>');
|
||||
});
|
||||
});
|
||||
|
||||
it('clamps tooltip position inside the viewport', async () => {
|
||||
const getBoundingClientRect = vi
|
||||
.spyOn(HTMLDivElement.prototype, 'getBoundingClientRect')
|
||||
.mockReturnValue({
|
||||
width: 180,
|
||||
height: 60,
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 180,
|
||||
bottom: 60,
|
||||
x: 0,
|
||||
y: 0,
|
||||
toJSON: () => ({}),
|
||||
} as DOMRect);
|
||||
|
||||
const raf = vi
|
||||
.spyOn(window, 'requestAnimationFrame')
|
||||
.mockImplementation((callback: FrameRequestCallback) => {
|
||||
callback(0);
|
||||
return 1;
|
||||
});
|
||||
|
||||
render(() => <Tooltip content="CPU" x={2} y={2} visible />);
|
||||
|
||||
const tooltip = document.body.querySelector('div[style*="opacity: 1"]') as HTMLDivElement | null;
|
||||
expect(tooltip).not.toBeNull();
|
||||
if (!tooltip) return;
|
||||
|
||||
await waitFor(() => {
|
||||
expect(Number.parseFloat(tooltip.style.left)).toBeGreaterThanOrEqual(4);
|
||||
expect(Number.parseFloat(tooltip.style.top)).toBeGreaterThanOrEqual(4);
|
||||
});
|
||||
|
||||
expect(raf).toHaveBeenCalled();
|
||||
expect(getBoundingClientRect).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('preserves the singleton tooltip API', async () => {
|
||||
const TooltipRoot = createTooltipSystem();
|
||||
render(() => <TooltipRoot />);
|
||||
|
||||
showTooltip('disk', 120, 80, { direction: 'down' });
|
||||
expect(await screen.findByText('disk')).toBeInTheDocument();
|
||||
|
||||
hideTooltip();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('disk')).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
64
frontend-modern/src/components/shared/tooltipModel.ts
Normal file
64
frontend-modern/src/components/shared/tooltipModel.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
export type TooltipAlignment = 'left' | 'center';
|
||||
export type TooltipDirection = 'up' | 'down';
|
||||
|
||||
export interface TooltipOptions {
|
||||
align?: TooltipAlignment;
|
||||
direction?: TooltipDirection;
|
||||
maxWidth?: number;
|
||||
}
|
||||
|
||||
export interface TooltipViewportRect {
|
||||
height: number;
|
||||
width: number;
|
||||
}
|
||||
|
||||
export interface TooltipPosition {
|
||||
left: number;
|
||||
top: number;
|
||||
}
|
||||
|
||||
interface ResolveTooltipPositionOptions extends TooltipOptions {
|
||||
rect: TooltipViewportRect;
|
||||
viewportHeight: number;
|
||||
viewportWidth: number;
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export function sanitizeTooltipContent(content: string): string {
|
||||
return content
|
||||
.replace(/<[^>]*>/g, '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
export function resolveTooltipPosition(options: ResolveTooltipPositionOptions): TooltipPosition {
|
||||
const padding = 8;
|
||||
const viewportPadding = 4;
|
||||
const align = options.align ?? 'center';
|
||||
const direction = options.direction ?? 'up';
|
||||
|
||||
let left = options.x;
|
||||
let top = options.y;
|
||||
|
||||
if (align === 'center') {
|
||||
left = options.x - options.rect.width / 2;
|
||||
}
|
||||
|
||||
if (direction === 'up') {
|
||||
top = options.y - options.rect.height - padding;
|
||||
} else {
|
||||
top = options.y + padding;
|
||||
}
|
||||
|
||||
const maxLeft = options.viewportWidth - options.rect.width - viewportPadding;
|
||||
const maxTop = options.viewportHeight - options.rect.height - viewportPadding;
|
||||
|
||||
left = Math.min(Math.max(left, viewportPadding), Math.max(maxLeft, viewportPadding));
|
||||
top = Math.min(Math.max(top, viewportPadding), Math.max(maxTop, viewportPadding));
|
||||
|
||||
return { left, top };
|
||||
}
|
||||
97
frontend-modern/src/components/shared/useTooltipState.ts
Normal file
97
frontend-modern/src/components/shared/useTooltipState.ts
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
import { createEffect, createSignal } from 'solid-js';
|
||||
import type { Accessor } from 'solid-js';
|
||||
import {
|
||||
resolveTooltipPosition,
|
||||
sanitizeTooltipContent,
|
||||
type TooltipOptions,
|
||||
type TooltipPosition,
|
||||
} from './tooltipModel';
|
||||
|
||||
interface TooltipStateOptions extends TooltipOptions {
|
||||
content: string;
|
||||
visible: boolean;
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
interface TooltipInstance {
|
||||
hide: () => void;
|
||||
show: (content: string, x: number, y: number, options?: TooltipOptions) => void;
|
||||
}
|
||||
|
||||
let tooltipInstance: TooltipInstance | null = null;
|
||||
|
||||
export function useTooltipState(options: TooltipStateOptions): {
|
||||
position: Accessor<TooltipPosition>;
|
||||
sanitizedContent: Accessor<string>;
|
||||
setTooltipRef: (el: HTMLDivElement) => void;
|
||||
} {
|
||||
let tooltipRef: HTMLDivElement | undefined;
|
||||
const [position, setPosition] = createSignal<TooltipPosition>({ left: 0, top: 0 });
|
||||
|
||||
createEffect(() => {
|
||||
if (!options.visible) {
|
||||
setPosition({ left: options.x, top: options.y });
|
||||
return;
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (!tooltipRef) return;
|
||||
|
||||
const rect = tooltipRef.getBoundingClientRect();
|
||||
setPosition(
|
||||
resolveTooltipPosition({
|
||||
align: options.align,
|
||||
direction: options.direction,
|
||||
rect,
|
||||
viewportHeight: window.innerHeight,
|
||||
viewportWidth: window.innerWidth,
|
||||
x: options.x,
|
||||
y: options.y,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
position,
|
||||
sanitizedContent: () => sanitizeTooltipContent(options.content),
|
||||
setTooltipRef: (el) => {
|
||||
tooltipRef = el;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function createTooltipSystemState() {
|
||||
const [visible, setVisible] = createSignal(false);
|
||||
const [content, setContent] = createSignal('');
|
||||
const [position, setPosition] = createSignal({ x: 0, y: 0 });
|
||||
const [options, setOptions] = createSignal<TooltipOptions>({});
|
||||
|
||||
tooltipInstance = {
|
||||
show: (contentValue, x, y, nextOptions) => {
|
||||
setContent(contentValue);
|
||||
setPosition({ x, y });
|
||||
setOptions(nextOptions ?? {});
|
||||
setVisible(true);
|
||||
},
|
||||
hide: () => {
|
||||
setVisible(false);
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
content,
|
||||
options,
|
||||
position,
|
||||
visible,
|
||||
};
|
||||
}
|
||||
|
||||
export function showTooltip(content: string, x: number, y: number, options?: TooltipOptions) {
|
||||
tooltipInstance?.show(content, x, y, options);
|
||||
}
|
||||
|
||||
export function hideTooltip() {
|
||||
tooltipInstance?.hide();
|
||||
}
|
||||
|
|
@ -32,6 +32,8 @@ import searchFieldSource from '@/components/shared/SearchField.tsx?raw';
|
|||
import searchFieldModelSource from '@/components/shared/searchFieldModel.ts?raw';
|
||||
import searchInputSource from '@/components/shared/SearchInput.tsx?raw';
|
||||
import searchInputModelSource from '@/components/shared/searchInputModel.ts?raw';
|
||||
import tooltipSource from '@/components/shared/Tooltip.tsx?raw';
|
||||
import tooltipModelSource from '@/components/shared/tooltipModel.ts?raw';
|
||||
import infrastructureSummaryTableSource from '@/components/shared/InfrastructureSummaryTable.tsx?raw';
|
||||
import infrastructureSummaryTableRowSource from '@/components/shared/InfrastructureSummaryTableRow.tsx?raw';
|
||||
import interactiveSparklineSource from '@/components/shared/InteractiveSparkline.tsx?raw';
|
||||
|
|
@ -48,6 +50,7 @@ import infrastructureSelectorStateSource from '@/components/shared/useInfrastruc
|
|||
import pulseDataGridStateSource from '@/components/shared/usePulseDataGridState.ts?raw';
|
||||
import searchFieldStateSource from '@/components/shared/useSearchFieldState.ts?raw';
|
||||
import searchInputStateSource from '@/components/shared/useSearchInputState.ts?raw';
|
||||
import tooltipStateSource from '@/components/shared/useTooltipState.ts?raw';
|
||||
import interactiveSparklineStateSource from '@/components/shared/useInteractiveSparklineState.ts?raw';
|
||||
import infrastructureSummaryTableStateSource from '@/components/shared/useInfrastructureSummaryTableState.ts?raw';
|
||||
import resourceBadgePresentationSource from '@/utils/resourceBadgePresentation.ts?raw';
|
||||
|
|
@ -2684,6 +2687,18 @@ describe('frontend resource type boundaries', () => {
|
|||
expect(searchInputStateSource).toContain('getSearchInputShortcutHint');
|
||||
expect(searchInputModelSource).toContain('getSearchInputShortcutHint');
|
||||
expect(searchInputModelSource).toContain('shouldSearchInputShowTrailingControls');
|
||||
expect(tooltipSource).toContain('useTooltipState');
|
||||
expect(tooltipSource).toContain('createTooltipSystemState');
|
||||
expect(tooltipSource).not.toContain('createSignal');
|
||||
expect(tooltipSource).not.toContain('requestAnimationFrame');
|
||||
expect(tooltipSource).not.toContain('sanitizeTooltipContent');
|
||||
expect(tooltipSource).not.toContain('resolveTooltipPosition');
|
||||
expect(tooltipStateSource).toContain('createSignal');
|
||||
expect(tooltipStateSource).toContain('requestAnimationFrame');
|
||||
expect(tooltipStateSource).toContain('tooltipInstance');
|
||||
expect(tooltipStateSource).toContain('resolveTooltipPosition');
|
||||
expect(tooltipModelSource).toContain('sanitizeTooltipContent');
|
||||
expect(tooltipModelSource).toContain('resolveTooltipPosition');
|
||||
expect(collapsibleSearchInputSource).toContain('useCollapsibleSearchInputState');
|
||||
expect(collapsibleSearchInputSource).not.toContain('createSignal');
|
||||
expect(collapsibleSearchInputSource).not.toContain('useTypeToSearch');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue