ContextMenu.test.tsx 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. /**
  2. * Tests for the ContextMenu component.
  3. */
  4. import { describe, it, expect, vi, beforeEach } from 'vitest';
  5. import { screen } from '@testing-library/react';
  6. import userEvent from '@testing-library/user-event';
  7. import { render } from '../utils';
  8. import { ContextMenu } from '../../components/ContextMenu';
  9. describe('ContextMenu', () => {
  10. const mockOnClose = vi.fn();
  11. const menuItems = [
  12. { label: 'Edit', onClick: vi.fn() },
  13. { label: 'Delete', onClick: vi.fn(), danger: true },
  14. { label: 'Download', onClick: vi.fn() },
  15. ];
  16. beforeEach(() => {
  17. vi.clearAllMocks();
  18. });
  19. describe('rendering', () => {
  20. it('renders menu items', () => {
  21. render(
  22. <ContextMenu
  23. x={100}
  24. y={100}
  25. items={menuItems}
  26. onClose={mockOnClose}
  27. />
  28. );
  29. expect(screen.getByText('Edit')).toBeInTheDocument();
  30. expect(screen.getByText('Delete')).toBeInTheDocument();
  31. expect(screen.getByText('Download')).toBeInTheDocument();
  32. });
  33. it('positions menu at specified coordinates', () => {
  34. render(
  35. <ContextMenu
  36. x={200}
  37. y={150}
  38. items={menuItems}
  39. onClose={mockOnClose}
  40. />
  41. );
  42. // Menu should be rendered with items visible
  43. expect(screen.getByText('Edit')).toBeInTheDocument();
  44. });
  45. });
  46. describe('interactions', () => {
  47. it('calls onClick when item is clicked', async () => {
  48. const user = userEvent.setup();
  49. render(
  50. <ContextMenu
  51. x={100}
  52. y={100}
  53. items={menuItems}
  54. onClose={mockOnClose}
  55. />
  56. );
  57. await user.click(screen.getByText('Edit'));
  58. expect(menuItems[0].onClick).toHaveBeenCalled();
  59. });
  60. it('calls onClose after item click', async () => {
  61. const user = userEvent.setup();
  62. render(
  63. <ContextMenu
  64. x={100}
  65. y={100}
  66. items={menuItems}
  67. onClose={mockOnClose}
  68. />
  69. );
  70. await user.click(screen.getByText('Edit'));
  71. expect(mockOnClose).toHaveBeenCalled();
  72. });
  73. });
  74. describe('styling', () => {
  75. it('applies danger styling', () => {
  76. render(
  77. <ContextMenu
  78. x={100}
  79. y={100}
  80. items={menuItems}
  81. onClose={mockOnClose}
  82. />
  83. );
  84. // Delete item has danger: true, so should have red styling
  85. const deleteButton = screen.getByText('Delete');
  86. expect(deleteButton).toBeInTheDocument();
  87. });
  88. });
  89. describe('dividers', () => {
  90. it('supports divider property on items', () => {
  91. // Just verify the ContextMenuItem interface accepts divider prop
  92. const itemsWithDivider = [
  93. { label: 'Edit', onClick: vi.fn() },
  94. { label: 'Copy', onClick: vi.fn(), divider: true },
  95. ];
  96. // Interface should accept these items without error
  97. expect(itemsWithDivider[1].divider).toBe(true);
  98. });
  99. });
  100. });