import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { store } from '@grafana/data'; import { ContentOutline } from './ContentOutline'; jest.mock('./ContentOutlineContext', () => ({ useContentOutlineContext: jest.fn(), })); const scrollIntoViewMock = jest.fn(); const scrollerMock = document.createElement('div'); const unregisterMock = jest.fn(); const setup = (mergeSingleChild = false) => { HTMLElement.prototype.scrollIntoView = scrollIntoViewMock; scrollerMock.scroll = jest.fn(); // Mock useContentOutlineContext with custom outlineItems const mockUseContentOutlineContext = require('./ContentOutlineContext').useContentOutlineContext; mockUseContentOutlineContext.mockReturnValue({ outlineItems: [ { id: 'item-1', icon: 'test-icon', title: 'Item 1', ref: document.createElement('div'), mergeSingleChild, children: [ { id: 'item-1-1', icon: 'test-icon', title: 'Item 1-1', ref: document.createElement('div'), level: 'child', }, ], }, { id: 'item-2', icon: 'test-icon', title: 'Item 2', ref: document.createElement('div'), mergeSingleChild, children: [ { id: 'item-2-1', icon: 'test-icon', title: 'Item 2-1', ref: document.createElement('div'), level: 'child', onRemove: () => unregisterMock('item-2-1'), }, { id: 'item-2-2', icon: 'test-icon', title: 'Item 2-2', ref: document.createElement('div'), level: 'child', }, ], }, ], register: jest.fn(), unregister: unregisterMock, }); return render(); }; describe('', () => { it('toggles content on button click', async () => { setup(); let showContentOutlineButton = screen.getByRole('button', { name: 'Collapse outline' }); expect(showContentOutlineButton).toBeInTheDocument(); await userEvent.click(showContentOutlineButton); const hideContentOutlineButton = screen.getByRole('button', { name: 'Expand outline' }); expect(hideContentOutlineButton).toBeInTheDocument(); await userEvent.click(hideContentOutlineButton); showContentOutlineButton = screen.getByRole('button', { name: 'Collapse outline' }); expect(showContentOutlineButton).toBeInTheDocument(); }); it('scrolls into view on content button click', async () => { setup(); const itemButtons = screen.getAllByRole('button', { name: /Item [0-9]+/ }); for (const button of itemButtons) { await userEvent.click(button); } expect(scrollerMock.scroll).toHaveBeenCalledTimes(itemButtons.length); }); it('doesnt merge a single child item when mergeSingleChild is false', async () => { setup(); const expandSectionChevrons = screen.getAllByRole('button', { name: 'Content outline item collapse button' }); await userEvent.click(expandSectionChevrons[0]); const child = screen.getByRole('button', { name: 'Item 1-1' }); expect(child).toBeInTheDocument(); }); it('merges a single child item when mergeSingleChild is true', () => { setup(true); const child = screen.queryByRole('button', { name: 'Item 1-1' }); expect(child).not.toBeInTheDocument(); }); it('displays multiple children', async () => { setup(); const expandSectionChevrons = screen.getAllByRole('button', { name: 'Content outline item collapse button' }); await userEvent.click(expandSectionChevrons[1]); const child1 = screen.getByRole('button', { name: 'Item 2-1' }); const child2 = screen.getByRole('button', { name: 'Item 2-2' }); expect(child1).toBeInTheDocument(); expect(child2).toBeInTheDocument(); }); it('if item has multiple children, it displays multiple children even when mergeSingleChild is true', async () => { setup(true); const expandSectionChevrons = screen.getAllByRole('button', { name: 'Content outline item collapse button' }); // since first item has only one child, we will have only one chevron await userEvent.click(expandSectionChevrons[0]); const child1 = screen.getByRole('button', { name: 'Item 2-1' }); const child2 = screen.getByRole('button', { name: 'Item 2-2' }); expect(child1).toBeInTheDocument(); expect(child2).toBeInTheDocument(); }); it('collapse button has same aria-controls as the section content', async () => { setup(); const expandSectionChevrons = screen.getAllByRole('button', { name: 'Content outline item collapse button' }); // chevron for the second item const button = expandSectionChevrons[1]; // content for the second item const sectionContent = screen.getByTestId('section-wrapper-item-2'); await userEvent.click(button); expect(button.getAttribute('aria-controls')).toBe(sectionContent.id); }); it('deletes item on delete button click', async () => { setup(); const expandSectionChevrons = screen.getAllByRole('button', { name: 'Content outline item collapse button' }); // chevron for the second item const button = expandSectionChevrons[1]; await userEvent.click(button); const deleteButtons = screen.getAllByTestId('content-outline-item-delete-button'); await userEvent.click(deleteButtons[0]); expect(unregisterMock).toHaveBeenCalledWith('item-2-1'); }); it('should retrieve the last expanded state from local storage', async () => { const getBoolMock = jest.spyOn(store, 'getBool').mockReturnValue(false); setup(); const collapseContentOutlineButton = screen.queryByRole('button', { name: 'Collapse outline' }); const expandContentOutlineButton = screen.queryByRole('button', { name: 'Expand outline' }); expect(collapseContentOutlineButton).not.toBeInTheDocument(); expect(expandContentOutlineButton).toBeInTheDocument(); getBoolMock.mockRestore(); }); });