import { describe, test, expect } from 'vitest' import { render, screen, fireEvent } from '@testing-library/react' import { ToolbarActions } from './toolbar-actions' import { IconButton, Divider } from '@mui/material' import { Tooltip } from '../../components/tooltip/tooltip' import { FileDownloadOutlined, ShareOutlined, EditOutlined, FilterListOutlined, } from '@mui/icons-material' describe('ToolbarActions', () => { const createAction = ( key: string, label: string, Icon: React.ComponentType, ) => ( ) describe('data-toolbar-hidden filtering', () => { test('filters children with data-toolbar-hidden from visible count', () => { const children = [ createAction('download', 'Download', FileDownloadOutlined), , createAction('share', 'Share', ShareOutlined), , createAction('edit', 'Edit', EditOutlined), ] render({children}) // Should show trigger button because there are 3 visible actions (download, share, edit) // even though total children count is 5 const triggerButton = screen.getByRole('button', { name: /more actions/i, }) expect(triggerButton).toBeTruthy() // Preview should include first 2 visible actions plus divider between them // (Download, Divider, Share - stops after counting 2 non-hidden actions) const downloadButtons = screen.getAllByLabelText('Download') const shareButtons = screen.getAllByLabelText('Share') expect(downloadButtons.length).toBeGreaterThanOrEqual(1) expect(shareButtons.length).toBeGreaterThanOrEqual(1) // Edit button should also be present (in the expanded section) const editButtons = screen.getAllByLabelText('Edit') expect(editButtons.length).toBeGreaterThanOrEqual(1) }) test('includes data-toolbar-hidden children in preview between visible actions', () => { const children = [ createAction('download', 'Download', FileDownloadOutlined), , createAction('share', 'Share', ShareOutlined), , createAction('edit', 'Edit', EditOutlined), ] const { container } = render( {children}, ) // With visibleCount={2}, preview should include: // Download, Divider-1, Share (counting 2 visible actions, including divider between them) // Hidden: Divider-2, Edit // First divider should be visible (in both preview and expanded) const allDividers = container.querySelectorAll( '[data-toolbar-hidden="true"]', ) expect(allDividers.length).toBeGreaterThanOrEqual(1) }) test('includes hidden children in expanded view', () => { const children = [ createAction('download', 'Download', FileDownloadOutlined), , createAction('share', 'Share', ShareOutlined), , createAction('edit', 'Edit', EditOutlined), ] const { container } = render( {children}, ) const triggerButton = screen.getByRole('button', { name: /more actions/i, }) fireEvent.click(triggerButton) // After expanding, all children including dividers should be present // Check for dividers with data-toolbar-hidden attribute // Note: dividers appear in both preview and expanded sections // Preview: Divider-1 (between Download and Share) // Expanded: Divider-1 and Divider-2 (all dividers) // Total: 3 dividers with data-toolbar-hidden attribute const dividers = container.querySelectorAll( '[data-toolbar-hidden="true"]', ) expect(dividers.length).toBe(3) // Divider-1 (preview) + Divider-1 + Divider-2 (expanded) }) test('does not show trigger when only hidden children exceed visibleCount', () => { const children = [ createAction('download', 'Download', FileDownloadOutlined), createAction('share', 'Share', ShareOutlined), , , ] render({children}) // Should not show trigger button because there are only 2 visible actions const triggerButtons = screen.queryAllByRole('button', { name: /more actions/i, }) expect(triggerButtons.length).toBe(0) // Both visible actions should be shown (appear in both preview and hidden expanded sections) const downloadButtons = screen.getAllByLabelText('Download') const shareButtons = screen.getAllByLabelText('Share') expect(downloadButtons.length).toBeGreaterThanOrEqual(1) expect(shareButtons.length).toBeGreaterThanOrEqual(1) }) }) describe('visibleCount behavior', () => { test('shows all actions when visibleCount is undefined', () => { const children = [ createAction('download', 'Download', FileDownloadOutlined), createAction('share', 'Share', ShareOutlined), createAction('edit', 'Edit', EditOutlined), createAction('filter', 'Filter', FilterListOutlined), ] render( {children}, ) // Should not show trigger button const triggerButtons = screen.queryAllByRole('button', { name: /more actions/i, }) expect(triggerButtons.length).toBe(0) // All actions should be visible (appear in both preview and hidden expanded sections) const downloadButtons = screen.getAllByLabelText('Download') const shareButtons = screen.getAllByLabelText('Share') const editButtons = screen.getAllByLabelText('Edit') const filterButtons = screen.getAllByLabelText('Filter') expect(downloadButtons.length).toBeGreaterThanOrEqual(1) expect(shareButtons.length).toBeGreaterThanOrEqual(1) expect(editButtons.length).toBeGreaterThanOrEqual(1) expect(filterButtons.length).toBeGreaterThanOrEqual(1) }) test('shows trigger when visibleCount is 1 and there are 2+ actions', () => { const children = [ createAction('download', 'Download', FileDownloadOutlined), createAction('share', 'Share', ShareOutlined), ] render({children}) // Should show trigger button const triggerButton = screen.getByRole('button', { name: /more actions/i, }) expect(triggerButton).toBeTruthy() // Download action should be visible (appears in both preview and expanded) const downloadButtons = screen.getAllByLabelText('Download') expect(downloadButtons.length).toBeGreaterThanOrEqual(1) // Share button exists in the expanded section const shareButtons = screen.getAllByLabelText('Share') expect(shareButtons.length).toBeGreaterThanOrEqual(1) }) test('shows trigger when visibleCount is 2 and there are 3+ actions', () => { const children = [ createAction('download', 'Download', FileDownloadOutlined), createAction('share', 'Share', ShareOutlined), createAction('edit', 'Edit', EditOutlined), ] render({children}) // Should show trigger button const triggerButton = screen.getByRole('button', { name: /more actions/i, }) expect(triggerButton).toBeTruthy() // First two actions should be visible (appear in both preview and expanded) const downloadButtons = screen.getAllByLabelText('Download') const shareButtons = screen.getAllByLabelText('Share') expect(downloadButtons.length).toBeGreaterThanOrEqual(1) expect(shareButtons.length).toBeGreaterThanOrEqual(1) }) test('does not show trigger when actions equal visibleCount', () => { const children = [ createAction('download', 'Download', FileDownloadOutlined), createAction('share', 'Share', ShareOutlined), ] render({children}) // Should not show trigger button const triggerButtons = screen.queryAllByRole('button', { name: /more actions/i, }) expect(triggerButtons.length).toBe(0) // Both actions should be visible (appear in both preview and hidden expanded sections) const downloadButtons = screen.getAllByLabelText('Download') const shareButtons = screen.getAllByLabelText('Share') expect(downloadButtons.length).toBeGreaterThanOrEqual(1) expect(shareButtons.length).toBeGreaterThanOrEqual(1) }) test('does not show trigger when actions are less than visibleCount', () => { const children = [ createAction('download', 'Download', FileDownloadOutlined), ] render({children}) // Should not show trigger button const triggerButtons = screen.queryAllByRole('button', { name: /more actions/i, }) expect(triggerButtons.length).toBe(0) // Single action should be visible (appears in both preview and hidden expanded sections) const downloadButtons = screen.getAllByLabelText('Download') expect(downloadButtons.length).toBeGreaterThanOrEqual(1) }) }) describe('trigger button behavior', () => { test('trigger button appears when there are more visible actions than visibleCount', () => { const children = [ createAction('download', 'Download', FileDownloadOutlined), createAction('share', 'Share', ShareOutlined), createAction('edit', 'Edit', EditOutlined), ] render({children}) const triggerButton = screen.getByRole('button', { name: /more actions/i, }) expect(triggerButton).toBeTruthy() }) test('trigger button does not appear when visibleCount is undefined', () => { const children = [ createAction('download', 'Download', FileDownloadOutlined), createAction('share', 'Share', ShareOutlined), createAction('edit', 'Edit', EditOutlined), ] render( {children}, ) const triggerButtons = screen.queryAllByRole('button', { name: /more actions/i, }) expect(triggerButtons.length).toBe(0) }) test('clicking trigger button expands the actions panel', () => { const children = [ createAction('download', 'Download', FileDownloadOutlined), createAction('share', 'Share', ShareOutlined), createAction('edit', 'Edit', EditOutlined), ] render({children}) const triggerButton = screen.getByRole('button', { name: /more actions/i, }) // Before clicking, trigger button should not have data-active expect(triggerButton.getAttribute('data-active')).toBe('false') fireEvent.click(triggerButton) // After clicking, trigger button should have data-active expect(triggerButton.getAttribute('data-active')).toBe('true') // Button should now show close label const closeButton = screen.getByRole('button', { name: /close/i }) expect(closeButton).toBeTruthy() }) test('clicking trigger button again collapses the actions panel', () => { const children = [ createAction('download', 'Download', FileDownloadOutlined), createAction('share', 'Share', ShareOutlined), createAction('edit', 'Edit', EditOutlined), ] render({children}) const triggerButton = screen.getByRole('button', { name: /more actions/i, }) // Expand fireEvent.click(triggerButton) expect(triggerButton.getAttribute('data-active')).toBe('true') // Collapse fireEvent.click(triggerButton) expect(triggerButton.getAttribute('data-active')).toBe('false') // Button should show trigger label again const reopenButton = screen.getByRole('button', { name: /more actions/i, }) expect(reopenButton).toBeTruthy() }) test('uses custom labels when provided', () => { const children = [ createAction('download', 'Download', FileDownloadOutlined), createAction('share', 'Share', ShareOutlined), createAction('edit', 'Edit', EditOutlined), ] render( {children} , ) const triggerButton = screen.getByRole('button', { name: /show options/i, }) expect(triggerButton).toBeTruthy() fireEvent.click(triggerButton) const closeButton = screen.getByRole('button', { name: /hide options/i }) expect(closeButton).toBeTruthy() }) }) describe('childrenPreview behavior', () => { test('childrenPreview shows only visible children up to visibleCount', () => { const children = [ createAction('download', 'Download', FileDownloadOutlined), createAction('share', 'Share', ShareOutlined), createAction('edit', 'Edit', EditOutlined), createAction('filter', 'Filter', FilterListOutlined), ] render({children}) // Only first 2 should be in preview // Note: Download and Share appear in both preview and expanded sections const downloadButtons = screen.getAllByLabelText('Download') const shareButtons = screen.getAllByLabelText('Share') expect(downloadButtons.length).toBeGreaterThanOrEqual(1) expect(shareButtons.length).toBeGreaterThanOrEqual(1) // Edit and Filter should also be in expanded section const triggerButton = screen.getByRole('button', { name: /more actions/i, }) fireEvent.click(triggerButton) // Now all should be visible const editButtons = screen.getAllByLabelText('Edit') const filterButtons = screen.getAllByLabelText('Filter') expect(editButtons.length).toBeGreaterThanOrEqual(1) expect(filterButtons.length).toBeGreaterThanOrEqual(1) }) test('childrenPreview includes hidden children but does not count them', () => { const children = [ createAction('download', 'Download', FileDownloadOutlined), , createAction('share', 'Share', ShareOutlined), createAction('edit', 'Edit', EditOutlined), ] const { container } = render( {children}, ) // Preview should show: Download, Divider, Share (2 visible actions counted, divider included) // Hidden: Edit action const downloadButtons = screen.getAllByLabelText('Download') const shareButtons = screen.getAllByLabelText('Share') expect(downloadButtons.length).toBeGreaterThanOrEqual(1) expect(shareButtons.length).toBeGreaterThanOrEqual(1) // Divider with data-toolbar-hidden should be in preview (appears in both preview and expanded) const allDividers = container.querySelectorAll( '[data-toolbar-hidden="true"]', ) expect(allDividers.length).toBeGreaterThanOrEqual(1) // Edit button should also be present in expanded section const triggerButton = screen.getByRole('button', { name: /more actions/i, }) fireEvent.click(triggerButton) const editButtons = screen.getAllByLabelText('Edit') expect(editButtons.length).toBeGreaterThanOrEqual(1) }) test('childrenPreview shows all visible children when visibleCount is undefined', () => { const children = [ createAction('download', 'Download', FileDownloadOutlined), createAction('share', 'Share', ShareOutlined), createAction('edit', 'Edit', EditOutlined), ] render( {children}, ) // All actions should be in preview (no trigger button) // Actions appear in both preview and hidden expanded sections const downloadButtons = screen.getAllByLabelText('Download') const shareButtons = screen.getAllByLabelText('Share') const editButtons = screen.getAllByLabelText('Edit') expect(downloadButtons.length).toBeGreaterThanOrEqual(1) expect(shareButtons.length).toBeGreaterThanOrEqual(1) expect(editButtons.length).toBeGreaterThanOrEqual(1) const triggerButtons = screen.queryAllByRole('button', { name: /more actions/i, }) expect(triggerButtons.length).toBe(0) }) }) describe('edge cases', () => { test('renders nothing when children array is empty', () => { const { container } = render({[]}) expect(container.firstChild).toBeNull() }) test('renders nothing when children is null', () => { const { container } = render({null}) expect(container.firstChild).toBeNull() }) test('handles single child (not array)', () => { const child = createAction('download', 'Download', FileDownloadOutlined) render({child}) // Should appear in both preview and expanded sections (even though expanded is hidden) const downloadButtons = screen.getAllByLabelText('Download') expect(downloadButtons.length).toBeGreaterThanOrEqual(1) const triggerButtons = screen.queryAllByRole('button', { name: /more actions/i, }) expect(triggerButtons.length).toBe(0) }) test('handles all children being hidden', () => { const children = [ , , ] const { container } = render( {children}, ) // Component still renders container even when no visible children // (it only checks children.length, not visibleChildren.length) // This test documents the current behavior expect(container.firstChild).toBeTruthy() // But there should be no trigger button since visibleChildren.length is 0 const triggerButtons = screen.queryAllByRole('button', { name: /more actions/i, }) expect(triggerButtons.length).toBe(0) }) test('correctly counts when mix of hidden and visible children', () => { const children = [ createAction('download', 'Download', FileDownloadOutlined), , createAction('share', 'Share', ShareOutlined), , createAction('edit', 'Edit', EditOutlined), , createAction('filter', 'Filter', FilterListOutlined), ] render({children}) // Should show trigger because there are 4 visible actions > 3 const triggerButton = screen.getByRole('button', { name: /more actions/i, }) expect(triggerButton).toBeTruthy() // Preview should show first 3 visible actions + dividers between them // (Download, Divider, Share, Divider, Edit - stops after counting 3 non-hidden) const downloadButtons = screen.getAllByLabelText('Download') const shareButtons = screen.getAllByLabelText('Share') const editButtons = screen.getAllByLabelText('Edit') expect(downloadButtons.length).toBeGreaterThanOrEqual(1) expect(shareButtons.length).toBeGreaterThanOrEqual(1) expect(editButtons.length).toBeGreaterThanOrEqual(1) }) test('documentation example: visibleCount={2} with [Action1, Divider, Action2, Divider, Action3]', () => { const children = [ createAction('action1', 'Action 1', FileDownloadOutlined), , createAction('action2', 'Action 2', ShareOutlined), , createAction('action3', 'Action 3', EditOutlined), ] const { container } = render( {children}, ) // Expected preview: Action1, Divider, Action2 (2 actions counted, divider included) // Hidden: Divider, Action3 // Should show trigger button since we have 3 visible actions > 2 const triggerButton = screen.getByRole('button', { name: /more actions/i, }) expect(triggerButton).toBeTruthy() // Action1 and Action2 should be visible const action1Buttons = screen.getAllByLabelText('Action 1') const action2Buttons = screen.getAllByLabelText('Action 2') expect(action1Buttons.length).toBeGreaterThanOrEqual(1) expect(action2Buttons.length).toBeGreaterThanOrEqual(1) // At least one divider with data-toolbar-hidden should be rendered const dividers = container.querySelectorAll( '[data-toolbar-hidden="true"]', ) expect(dividers.length).toBeGreaterThanOrEqual(1) // Action3 should be in expanded section const action3Buttons = screen.getAllByLabelText('Action 3') expect(action3Buttons.length).toBeGreaterThanOrEqual(1) }) }) describe('direction prop', () => { test('applies correct flex direction for left expansion', () => { const children = [ createAction('download', 'Download', FileDownloadOutlined), createAction('share', 'Share', ShareOutlined), ] const { container } = render( {children}, ) const root = container.firstChild as HTMLElement expect(root).toBeTruthy() // Check that the style contains row-reverse const style = window.getComputedStyle(root) expect(style.flexDirection).toBe('row-reverse') }) test('applies correct flex direction for right expansion (default)', () => { const children = [ createAction('download', 'Download', FileDownloadOutlined), createAction('share', 'Share', ShareOutlined), ] const { container } = render( {children}, ) const root = container.firstChild as HTMLElement expect(root).toBeTruthy() const style = window.getComputedStyle(root) expect(style.flexDirection).toBe('row') }) }) })