index.spec.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. import {ProjectFixture} from 'sentry-fixture/project';
  2. import {render, screen, userEvent, within} from 'sentry-test/reactTestingLibrary';
  3. import {ViewHierarchy} from '.';
  4. // Mocks for useVirtualizedTree hook
  5. class ResizeObserver {
  6. observe() {}
  7. unobserve() {}
  8. disconnect() {}
  9. }
  10. window.ResizeObserver = ResizeObserver;
  11. window.Element.prototype.scrollTo = jest.fn();
  12. window.Element.prototype.scrollIntoView = jest.fn();
  13. const DEFAULT_VALUES = {alpha: 1, height: 1, width: 1, x: 1, y: 1, visible: true};
  14. const DEFAULT_MOCK_DATA = {
  15. rendering_system: 'test-rendering-system',
  16. windows: [
  17. {
  18. ...DEFAULT_VALUES,
  19. type: 'Container',
  20. identifier: 'test_identifier',
  21. x: 200,
  22. children: [
  23. {
  24. ...DEFAULT_VALUES,
  25. type: 'Nested Container',
  26. x: 10,
  27. y: 10,
  28. width: 3,
  29. height: 4,
  30. identifier: 'nested',
  31. children: [
  32. {
  33. ...DEFAULT_VALUES,
  34. type: 'Text',
  35. children: [],
  36. },
  37. ],
  38. },
  39. ],
  40. },
  41. ],
  42. };
  43. describe('View Hierarchy', function () {
  44. let MOCK_DATA;
  45. let project;
  46. beforeEach(() => {
  47. MOCK_DATA = DEFAULT_MOCK_DATA;
  48. project = ProjectFixture();
  49. });
  50. it('can continue make selections for inspecting data', async function () {
  51. render(<ViewHierarchy viewHierarchy={MOCK_DATA} project={project} />);
  52. // 1 for the tree node, 1 for the details panel header
  53. expect(screen.getAllByText('Container - test_identifier')).toHaveLength(2);
  54. await userEvent.click(screen.getByText('Nested Container - nested'));
  55. // 1 for the tree node, 1 for the details panel header
  56. expect(screen.getAllByText('Nested Container - nested')).toHaveLength(2);
  57. // Only visible in the tree node
  58. expect(screen.getByText('Container - test_identifier')).toBeInTheDocument();
  59. await userEvent.click(screen.getByText('Text'));
  60. // 1 for the tree node, 1 for the details panel header, 1 for the details value
  61. expect(screen.getAllByText('Text')).toHaveLength(3);
  62. // Only visible in the tree node
  63. expect(screen.getByText('Nested Container - nested')).toBeInTheDocument();
  64. });
  65. it('can expand and collapse by clicking the icon', async function () {
  66. render(<ViewHierarchy viewHierarchy={MOCK_DATA} project={project} />);
  67. expect(screen.queryByText('Text')).toBeInTheDocument();
  68. await userEvent.click(
  69. within(screen.getByLabelText('Nested Container - nested')).getByRole('button', {
  70. name: 'Collapse',
  71. })
  72. );
  73. expect(screen.queryByText('Text')).not.toBeInTheDocument();
  74. await userEvent.click(screen.getByRole('button', {name: 'Expand'}));
  75. expect(screen.queryByText('Text')).toBeInTheDocument();
  76. });
  77. it('can navigate with keyboard shortcuts after a selection', async function () {
  78. render(<ViewHierarchy viewHierarchy={MOCK_DATA} project={project} />);
  79. await userEvent.click(screen.getAllByText('Container - test_identifier')[0]);
  80. await userEvent.keyboard('{ArrowDown}');
  81. // 1 for the tree node, 1 for the details panel header
  82. expect(screen.getAllByText('Nested Container - nested')).toHaveLength(2);
  83. });
  84. it('can expand/collapse with the keyboard', async function () {
  85. render(<ViewHierarchy viewHierarchy={MOCK_DATA} project={project} />);
  86. await userEvent.click(screen.getAllByText('Nested Container - nested')[0]);
  87. await userEvent.keyboard('{Enter}');
  88. expect(screen.queryByText('Text')).not.toBeInTheDocument();
  89. await userEvent.keyboard('{Enter}');
  90. expect(screen.getByText('Text')).toBeInTheDocument();
  91. });
  92. it('can render multiple windows together', function () {
  93. MOCK_DATA.windows = [
  94. ...MOCK_DATA.windows,
  95. {
  96. ...DEFAULT_VALUES,
  97. type: 'Second Window',
  98. children: [
  99. {
  100. ...DEFAULT_VALUES,
  101. type: 'Second Window Child',
  102. children: [],
  103. },
  104. ],
  105. },
  106. ];
  107. render(<ViewHierarchy viewHierarchy={MOCK_DATA} project={project} />);
  108. expect(screen.getByText('Second Window')).toBeInTheDocument();
  109. expect(screen.getByText('Second Window Child')).toBeInTheDocument();
  110. });
  111. it('does not render the wireframe for the Unity platform', function () {
  112. const mockUnityProject = ProjectFixture({platform: 'unity'});
  113. render(<ViewHierarchy viewHierarchy={MOCK_DATA} project={mockUnityProject} />);
  114. expect(screen.queryByTestId('view-hierarchy-wireframe')).not.toBeInTheDocument();
  115. });
  116. it('draws the selected node when a tree selection is made', async function () {
  117. render(<ViewHierarchy viewHierarchy={MOCK_DATA} project={project} />);
  118. const canvas = screen.getByTestId(
  119. 'view-hierarchy-wireframe-overlay'
  120. ) as HTMLCanvasElement;
  121. const context = canvas.getContext('2d');
  122. if (!context) {
  123. throw new Error('Canvas context is not defined');
  124. }
  125. expect(context.fillRect).not.toHaveBeenCalledWith(210, 11, 3, 4);
  126. await userEvent.click(screen.getByText('Nested Container - nested'));
  127. // This is the nested container, the x, y positions are shifted by the parent
  128. expect(context.fillRect).toHaveBeenCalledWith(210, 11, 3, 4);
  129. });
  130. it('does not render a wireframe selection initially', function () {
  131. render(<ViewHierarchy viewHierarchy={MOCK_DATA} project={project} />);
  132. const canvas = screen.getByTestId(
  133. 'view-hierarchy-wireframe-overlay'
  134. ) as HTMLCanvasElement;
  135. const context = canvas.getContext('2d');
  136. if (!context) {
  137. throw new Error('Canvas context is not defined');
  138. }
  139. // The overlay should not have rendered anything before any interactions
  140. expect(context.fillRect).not.toHaveBeenCalled();
  141. });
  142. it('renders an empty state if there is no data in windows to visualize', function () {
  143. render(
  144. <ViewHierarchy
  145. viewHierarchy={{rendering_system: 'This can be anything', windows: []}}
  146. project={project}
  147. />
  148. );
  149. expect(
  150. screen.getByText('There is no view hierarchy data to visualize')
  151. ).toBeInTheDocument();
  152. });
  153. it('renders with depth markers', function () {
  154. render(<ViewHierarchy viewHierarchy={MOCK_DATA} project={project} />);
  155. });
  156. it('renders an icon with a tooltip for the rendering system', async function () {
  157. MOCK_DATA.rendering_system = 'flutter';
  158. render(<ViewHierarchy viewHierarchy={MOCK_DATA} project={project} />);
  159. await userEvent.hover(screen.getByTestId('rendering-system-icon'));
  160. expect(await screen.findByText('Rendering System: flutter')).toBeInTheDocument();
  161. });
  162. });