import React from 'react';
import { vi, describe, it, expect, test, beforeEach, type Mock } from 'vitest';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { showModal, useConfig, useOnClickOutside } from '@openmrs/esm-framework';
import { mockStickyNote } from '__mocks__';
import { mockPatient } from 'tools';
import { type ConfigObject } from '../config-schema';
import { useStickyNote } from './sticky-note.resource';
import StickyNoteHeaderButton from './sticky-note-header-button.component';
vi.mock('./sticky-note.resource', () => ({
useStickyNote: vi.fn(),
}));
vi.mock('./sticky-note-panel.component', () => ({ default: () =>
}));
const mockUseStickyNote = useStickyNote as Mock;
const mockShowModal = showModal as Mock;
const mockUseOnClickOutside = useOnClickOutside as Mock;
const mockUseConfig = vi.mocked(useConfig);
// The framework's jest mock of useOnClickOutside is a no-op. Swap in a minimal real
// implementation so the "closes on outside click" test can exercise the wiring.
const useRealOnClickOutside = (handler: (e: MouseEvent) => void, active: boolean) => {
const ref = React.useRef(null);
React.useEffect(() => {
if (!active) return;
const listener = (e: MouseEvent) => {
if (ref.current && e.target instanceof Node && ref.current.contains(e.target)) return;
handler(e);
};
window.addEventListener('mousedown', listener);
return () => window.removeEventListener('mousedown', listener);
}, [handler, active]);
return ref;
};
describe('StickyNoteHeaderButton', () => {
const patientUuid = mockPatient.id;
beforeEach(() => {
mockUseConfig.mockReturnValue({ stickyNoteConceptUuid: 'concept-uuid' } as ConfigObject);
});
it('does not render when stickyNoteConceptUuid is disabled via empty string', () => {
mockUseConfig.mockReturnValue({ stickyNoteConceptUuid: '' } as ConfigObject);
mockUseStickyNote.mockReturnValue({ note: mockStickyNote, isLoading: false, error: undefined, mutate: vi.fn() });
const { container } = render();
expect(container).toBeEmptyDOMElement();
expect(screen.queryByRole('button', { name: /sticky note/i })).not.toBeInTheDocument();
});
it('renders a button labelled "Sticky note"', () => {
mockUseStickyNote.mockReturnValue({ note: mockStickyNote, isLoading: false, error: undefined, mutate: vi.fn() });
render();
expect(screen.getByRole('button', { name: /sticky note/i })).toBeInTheDocument();
});
it('shows a "1" badge when a note exists', () => {
mockUseStickyNote.mockReturnValue({ note: mockStickyNote, isLoading: false, error: undefined, mutate: vi.fn() });
render();
expect(screen.getByText('1')).toBeInTheDocument();
});
it('does not show a badge when no note exists', () => {
mockUseStickyNote.mockReturnValue({ note: undefined, isLoading: false, error: undefined, mutate: vi.fn() });
render();
expect(screen.queryByText('1')).not.toBeInTheDocument();
});
it('toggles the panel on click when a note exists', async () => {
const user = userEvent.setup();
mockUseStickyNote.mockReturnValue({ note: mockStickyNote, isLoading: false, error: undefined, mutate: vi.fn() });
render();
const button = screen.getByRole('button', { name: /sticky note/i });
expect(screen.queryByTestId('sticky-note-panel')).not.toBeInTheDocument();
await user.click(button);
expect(screen.getByTestId('sticky-note-panel')).toBeInTheDocument();
await user.click(button);
expect(screen.queryByTestId('sticky-note-panel')).not.toBeInTheDocument();
});
it('opens the create modal on click when no note exists', async () => {
const user = userEvent.setup();
const mutate = vi.fn();
mockUseStickyNote.mockReturnValue({ note: undefined, isLoading: false, error: undefined, mutate });
render();
await user.click(screen.getByRole('button', { name: /sticky note/i }));
expect(mockShowModal).toHaveBeenCalledWith('sticky-note-modal', expect.objectContaining({ patientUuid, mutate }));
expect(screen.queryByTestId('sticky-note-panel')).not.toBeInTheDocument();
});
it('opens the panel (not the create modal) while loading', async () => {
const user = userEvent.setup();
mockUseStickyNote.mockReturnValue({ note: undefined, isLoading: true, error: undefined, mutate: vi.fn() });
render();
await user.click(screen.getByRole('button', { name: /sticky note/i }));
expect(mockShowModal).not.toHaveBeenCalled();
expect(screen.getByTestId('sticky-note-panel')).toBeInTheDocument();
});
it('opens the panel (not the create modal) when the fetch failed', async () => {
const user = userEvent.setup();
mockUseStickyNote.mockReturnValue({
note: undefined,
isLoading: false,
error: new Error('Failed'),
mutate: vi.fn(),
});
render();
await user.click(screen.getByRole('button', { name: /sticky note/i }));
expect(mockShowModal).not.toHaveBeenCalled();
expect(screen.getByTestId('sticky-note-panel')).toBeInTheDocument();
});
it('closes the panel on Escape', async () => {
const user = userEvent.setup();
mockUseStickyNote.mockReturnValue({ note: mockStickyNote, isLoading: false, error: undefined, mutate: vi.fn() });
render();
await user.click(screen.getByRole('button', { name: /sticky note/i }));
expect(screen.getByTestId('sticky-note-panel')).toBeInTheDocument();
await user.keyboard('{Escape}');
expect(screen.queryByTestId('sticky-note-panel')).not.toBeInTheDocument();
});
it('closes the panel on outside click', async () => {
const user = userEvent.setup();
mockUseStickyNote.mockReturnValue({ note: mockStickyNote, isLoading: false, error: undefined, mutate: vi.fn() });
mockUseOnClickOutside.mockImplementation(useRealOnClickOutside);
render(
,
);
await user.click(screen.getByRole('button', { name: /sticky note/i }));
expect(screen.getByTestId('sticky-note-panel')).toBeInTheDocument();
await user.click(screen.getByRole('button', { name: /outside/i }));
expect(screen.queryByTestId('sticky-note-panel')).not.toBeInTheDocument();
});
it('ignores outside clicks that land inside an open modal', async () => {
const user = userEvent.setup();
mockUseStickyNote.mockReturnValue({ note: mockStickyNote, isLoading: false, error: undefined, mutate: vi.fn() });
mockUseOnClickOutside.mockImplementation(useRealOnClickOutside);
render(
,
);
await user.click(screen.getByRole('button', { name: /sticky note/i }));
expect(screen.getByTestId('sticky-note-panel')).toBeInTheDocument();
await user.click(screen.getByRole('button', { name: /in modal/i }));
expect(screen.getByTestId('sticky-note-panel')).toBeInTheDocument();
});
});