diff --git a/src/components/GalleryTooltip/GalleryTooltip.test.tsx b/src/components/GalleryTooltip/GalleryTooltip.test.tsx
new file mode 100644
index 0000000..5cac6fc
--- /dev/null
+++ b/src/components/GalleryTooltip/GalleryTooltip.test.tsx
@@ -0,0 +1,168 @@
+import Tooltip from '@mui/material/Tooltip';
+import { act, fireEvent, render, screen } from '@testing-library/react';
+import type { Mock } from 'vitest';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+
+import GalleryTooltip from './GalleryTooltip';
+
+// Mock MUI Tooltip to capture props and still render children
+vi.mock('@mui/material/Tooltip', () => {
+ const mock = vi.fn((props: any) =>
{props.children}
);
+ return { __esModule: true, default: mock };
+});
+
+// Helper to mock matchMedia for touch vs non-touch devices
+const mockMatchMedia = (isTouch: boolean) => {
+ const mql = (query: string) => ({
+ matches: isTouch && query === '(hover: none)',
+ media: query,
+ onchange: null as any,
+ addListener: vi.fn(), // deprecated
+ removeListener: vi.fn(), // deprecated
+ addEventListener: vi.fn(),
+ removeEventListener: vi.fn(),
+ dispatchEvent: vi.fn(),
+ });
+ Object.defineProperty(window, 'matchMedia', {
+ writable: true,
+ value: vi.fn(mql),
+ });
+};
+
+const getLastTooltipProps = () => {
+ const calls = (Tooltip as unknown as Mock).mock.calls;
+ return calls[calls.length - 1]?.[0] ?? {};
+};
+
+beforeEach(() => {
+ (Tooltip as unknown as Mock).mockClear();
+});
+
+describe('GalleryTooltip', () => {
+ it('renders its children', () => {
+ mockMatchMedia(false);
+ render(
+
+
+
+ );
+ expect(screen.getByText('Child')).toBeInTheDocument();
+ });
+
+ it('is closed by default and uses right placement by default', () => {
+ mockMatchMedia(false);
+ render(
+
+
+
+ );
+ const props = getLastTooltipProps();
+ expect(props.open).toBe(false);
+ expect(props.placement).toBe('right');
+ expect(props.disableFocusListener).toBe(true);
+ });
+
+ it('respects custom placement', () => {
+ mockMatchMedia(false);
+ render(
+
+
+
+ );
+ const props = getLastTooltipProps();
+ expect(props.placement).toBe('top');
+ });
+
+ it('on non-touch: mouseenter opens, mouseleave closes', () => {
+ mockMatchMedia(false);
+ render(
+
+ Trigger
+
+ );
+
+ const span = screen.getByText('Trigger').closest('span') ?? screen.getByText('Trigger');
+ expect(getLastTooltipProps().open).toBe(false);
+
+ fireEvent.mouseEnter(span as Element);
+ expect(getLastTooltipProps().open).toBe(true);
+
+ fireEvent.mouseLeave(span as Element);
+ expect(getLastTooltipProps().open).toBe(false);
+ });
+
+ it('on touch devices: click toggles open/close; hover ignored', () => {
+ mockMatchMedia(true);
+ render(
+
+ Trigger
+
+ );
+
+ const span = screen.getByText('Trigger').closest('span') ?? screen.getByText('Trigger');
+ expect(getLastTooltipProps().open).toBe(false);
+
+ fireEvent.mouseEnter(span as Element);
+ expect(getLastTooltipProps().open).toBe(false);
+
+ fireEvent.click(span as Element);
+ expect(getLastTooltipProps().open).toBe(true);
+
+ fireEvent.click(span as Element);
+ expect(getLastTooltipProps().open).toBe(false);
+ });
+
+ it('forwards onOpen/onClose to control internal state', async () => {
+ mockMatchMedia(false);
+ render(
+
+ Trigger
+
+ );
+
+ const firstProps = getLastTooltipProps();
+ await act(async () => {
+ firstProps.onOpen?.({});
+ });
+ expect(getLastTooltipProps().open).toBe(true);
+
+ await act(async () => {
+ getLastTooltipProps().onClose?.({});
+ });
+ expect(getLastTooltipProps().open).toBe(false);
+ });
+
+ it('passes correct disableHoverListener/disableTouchListener based on device', () => {
+ mockMatchMedia(false);
+ render(
+
+ Trigger
+
+ );
+ let props = getLastTooltipProps();
+ expect(props.disableHoverListener).toBe(true);
+ expect(props.disableTouchListener).toBe(false);
+
+ (Tooltip as unknown as Mock).mockClear();
+ mockMatchMedia(true);
+ render(
+
+ Trigger
+
+ );
+ props = getLastTooltipProps();
+ expect(props.disableHoverListener).toBe(false);
+ expect(props.disableTouchListener).toBe(true);
+ });
+
+ it('wraps content inside Typography (via title prop)', () => {
+ mockMatchMedia(false);
+ render(
+
+ Trigger
+
+ );
+ const props = getLastTooltipProps();
+ expect(props.title?.props?.children).toBe('Tooltip content');
+ });
+});
\ No newline at end of file