timelineZoom.spec.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. import {act, fireEvent, render, screen, waitFor} from 'sentry-test/reactTestingLibrary';
  2. import {useTimelineZoom} from './timelineZoom';
  3. interface TestProps {
  4. onSelect?: (startX: number, endX: number) => void;
  5. }
  6. function TestComponent({onSelect}: TestProps) {
  7. const {isActive, timelineSelector, selectionContainerRef} =
  8. useTimelineZoom<HTMLDivElement>({enabled: true, onSelect});
  9. return (
  10. <div data-test-id="body">
  11. {isActive && <div>Selection Active</div>}
  12. <div data-test-id="container" ref={selectionContainerRef}>
  13. {timelineSelector}
  14. </div>
  15. </div>
  16. );
  17. }
  18. beforeEach(() => {
  19. jest
  20. .spyOn(window, 'requestAnimationFrame')
  21. .mockImplementation((callback: FrameRequestCallback): number => {
  22. callback(0);
  23. return 0;
  24. });
  25. });
  26. afterEach(() => {
  27. jest.mocked(window.requestAnimationFrame).mockRestore();
  28. });
  29. function setupTestComponent() {
  30. const handleSelect = jest.fn();
  31. render(<TestComponent onSelect={handleSelect} />);
  32. const body = screen.getByTestId('body');
  33. const container = screen.getByTestId('container');
  34. container.getBoundingClientRect = jest.fn(() => ({
  35. x: 10,
  36. y: 10,
  37. width: 100,
  38. height: 100,
  39. left: 10,
  40. top: 10,
  41. right: 110,
  42. bottom: 110,
  43. toJSON: jest.fn(),
  44. }));
  45. return {handleSelect, body, container};
  46. }
  47. describe('TimelineZoom', function () {
  48. it('triggers onSelect', async function () {
  49. const {handleSelect, body, container} = setupTestComponent();
  50. // Selector has not appeared
  51. expect(screen.queryByRole('presentation')).not.toBeInTheDocument();
  52. // Selection does not start when clicking outside the container
  53. act(() => fireEvent.mouseDown(body, {button: 0, clientX: 0, clientY: 0}));
  54. expect(screen.queryByRole('presentation')).not.toBeInTheDocument();
  55. // Move cursor into the container, selection still not present
  56. act(() => fireEvent.mouseMove(body, {clientX: 20, clientY: 20}));
  57. expect(screen.queryByRole('presentation')).not.toBeInTheDocument();
  58. // Left click starts selection
  59. act(() => fireEvent.mouseDown(body, {button: 0, clientX: 20, clientY: 20}));
  60. const selection = screen.getByRole('presentation');
  61. expect(selection).toBeInTheDocument();
  62. expect(screen.getByText('Selection Active')).toBeInTheDocument();
  63. expect(container.style.getPropertyValue('--selectionStart')).toBe('10px');
  64. expect(container.style.getPropertyValue('--selectionWidth')).toBe('0px');
  65. // Body has disabled text selection
  66. expect(document.body).toHaveStyle({userSelect: 'none'});
  67. // Move right 15px
  68. act(() => fireEvent.mouseMove(body, {clientX: 35, clientY: 20}));
  69. expect(container.style.getPropertyValue('--selectionWidth')).toBe('15px');
  70. // Move left 25px, at the edge of the container
  71. act(() => fireEvent.mouseMove(body, {clientX: 10, clientY: 20}));
  72. expect(container.style.getPropertyValue('--selectionStart')).toBe('0px');
  73. expect(container.style.getPropertyValue('--selectionWidth')).toBe('10px');
  74. // Move left 5px more, selection does not move out of the container
  75. act(() => fireEvent.mouseMove(body, {clientX: 5, clientY: 20}));
  76. expect(container.style.getPropertyValue('--selectionStart')).toBe('0px');
  77. expect(container.style.getPropertyValue('--selectionWidth')).toBe('10px');
  78. // Release to make selection
  79. act(() => {
  80. fireEvent.mouseUp(body, {clientX: 5, clientY: 20});
  81. });
  82. await waitFor(() => {
  83. expect(handleSelect).toHaveBeenCalledWith(0, 10);
  84. });
  85. });
  86. it('does not start selection with right click', function () {
  87. const {body} = setupTestComponent();
  88. // Move cursor into the container, selection still not present
  89. fireEvent.mouseMove(body, {clientX: 20, clientY: 20});
  90. // Right click does nothing
  91. fireEvent.mouseDown(body, {button: 1, clientX: 20, clientY: 20});
  92. expect(screen.queryByRole('presentation')).not.toBeInTheDocument();
  93. });
  94. it('does not select for very small regions', async function () {
  95. const {handleSelect, body, container} = setupTestComponent();
  96. // Left click starts selection
  97. act(() => fireEvent.mouseMove(body, {clientX: 20, clientY: 20}));
  98. act(() => fireEvent.mouseDown(body, {button: 0, clientX: 20, clientY: 20}));
  99. act(() => fireEvent.mouseMove(body, {clientX: 22, clientY: 20}));
  100. const selection = screen.getByRole('presentation');
  101. expect(selection).toBeInTheDocument();
  102. expect(screen.getByText('Selection Active')).toBeInTheDocument();
  103. expect(container.style.getPropertyValue('--selectionStart')).toBe('10px');
  104. expect(container.style.getPropertyValue('--selectionWidth')).toBe('2px');
  105. // Relase does not make selection for such a small range
  106. act(() => fireEvent.mouseUp(body, {clientX: 22, clientY: 20}));
  107. // Can't wait for the handleSelect to be called, as it's not called
  108. await act(tick);
  109. expect(handleSelect).not.toHaveBeenCalledWith(0, 10);
  110. });
  111. });