useVirtualizedTree.spec.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. import {reactHooks, waitFor} from 'sentry-test/reactTestingLibrary';
  2. import {useVirtualizedTree} from 'sentry/utils/profiling/hooks/useVirtualizedTree/useVirtualizedTree';
  3. const n = d => {
  4. return {...d, children: []};
  5. };
  6. // Creates a tree with N nodes where each node has only one child
  7. const chain = (prefix: string, depth: number) => {
  8. let node = n({id: `${prefix}-0`});
  9. let start = 1;
  10. // Keep a root reference so we can return it
  11. const root = node;
  12. // Build a tree of nodes with each node having only one child
  13. while (start < depth) {
  14. const child = n({id: `${prefix}-${start}`});
  15. node.children = [child];
  16. // Swap the current node
  17. node = child;
  18. start++;
  19. }
  20. return root;
  21. };
  22. class ResizeObserver {
  23. observe() {}
  24. unobserve() {}
  25. disconnect() {}
  26. }
  27. window.ResizeObserver = ResizeObserver;
  28. window.requestAnimationFrame = (cb: Function) => cb();
  29. const makeScrollContainerMock = ({height}: {height: number}) => {
  30. return {
  31. getBoundingClientRect: () => {
  32. return {height};
  33. },
  34. addEventListener: () => {},
  35. removeEventListener: () => {},
  36. } as unknown as HTMLElement;
  37. };
  38. describe('useVirtualizedTree', () => {
  39. it('returns a tree', () => {
  40. const results = reactHooks.renderHook(() =>
  41. useVirtualizedTree({
  42. overscroll: 0,
  43. rowHeight: 10,
  44. tree: [],
  45. scrollContainer: null,
  46. renderRow: () => <div />,
  47. })
  48. );
  49. expect(results.result.current.items).toEqual([]);
  50. });
  51. it('shows first 10 items', () => {
  52. const mockScrollContainer = makeScrollContainerMock({height: 100});
  53. const tree = [chain('child', 10)];
  54. const {result} = reactHooks.renderHook(() =>
  55. useVirtualizedTree({
  56. rowHeight: 10,
  57. scrollContainer: mockScrollContainer,
  58. overscroll: 0,
  59. tree,
  60. renderRow: () => <div />,
  61. })
  62. );
  63. reactHooks.act(() => {
  64. result.current.handleExpandTreeNode(result.current.tree.roots[0], {
  65. expandChildren: true,
  66. });
  67. });
  68. for (let i = 0; i < 10; i++) {
  69. expect(result.current.items[i].item.node.id).toEqual(`child-${i}`);
  70. }
  71. expect(result.current.items.length).toBe(10);
  72. });
  73. it('shows 5-15 items', async () => {
  74. const mockScrollContainer = makeScrollContainerMock({height: 100});
  75. const tree = [chain('child', 20)];
  76. const {result} = reactHooks.renderHook(() =>
  77. useVirtualizedTree({
  78. rowHeight: 10,
  79. scrollContainer: mockScrollContainer,
  80. overscroll: 0,
  81. tree,
  82. renderRow: () => <div />,
  83. })
  84. );
  85. reactHooks.act(() => {
  86. result.current.handleExpandTreeNode(result.current.tree.roots[0], {
  87. expandChildren: true,
  88. });
  89. result.current.dispatch({type: 'set scroll top', payload: 50});
  90. });
  91. await waitFor(() => {
  92. expect(result.current.items.length).toBe(10);
  93. });
  94. for (let i = 0; i < 10; i++) {
  95. expect(result.current.items[i].item.node.id).toEqual(`child-${i + 5}`);
  96. }
  97. expect(result.current.items.length).toBe(10);
  98. });
  99. it('shows last 10 items', async () => {
  100. const mockScrollContainer = makeScrollContainerMock({height: 100});
  101. const tree = [chain('child', 20)];
  102. const {result} = reactHooks.renderHook(() =>
  103. useVirtualizedTree({
  104. rowHeight: 10,
  105. scrollContainer: mockScrollContainer,
  106. overscroll: 0,
  107. tree,
  108. renderRow: () => <div />,
  109. })
  110. );
  111. reactHooks.act(() => {
  112. result.current.handleExpandTreeNode(result.current.tree.roots[0], {
  113. expandChildren: true,
  114. });
  115. result.current.dispatch({type: 'set scroll top', payload: 100});
  116. });
  117. await waitFor(() => {
  118. expect(result.current.items.length).toBe(10);
  119. });
  120. for (let i = 0; i < 10; i++) {
  121. expect(result.current.items[i].item.node.id).toEqual(`child-${i + 10}`);
  122. }
  123. expect(result.current.items.length).toBe(10);
  124. });
  125. it('shows overscroll items', () => {
  126. const mockScrollContainer = makeScrollContainerMock({height: 100});
  127. const tree = [chain('child', 20)];
  128. const {result} = reactHooks.renderHook(() =>
  129. useVirtualizedTree({
  130. rowHeight: 10,
  131. scrollContainer: mockScrollContainer,
  132. overscroll: 2,
  133. tree,
  134. renderRow: () => <div />,
  135. })
  136. );
  137. reactHooks.act(() => {
  138. result.current.handleExpandTreeNode(result.current.tree.roots[0], {
  139. expandChildren: true,
  140. });
  141. result.current.dispatch({type: 'set scroll top', payload: 50});
  142. });
  143. for (let i = 3; i < 17; i++) {
  144. // Should display nodes 5-15, but since we use overscroll, it should display nodes 3-17
  145. expect(result.current.items[i - 3].item.node.id).toEqual(`child-${i}`);
  146. }
  147. expect(result.current.items.length).toBe(14);
  148. });
  149. it('items have a stable key', () => {
  150. const mockScrollContainer = makeScrollContainerMock({height: 100});
  151. const tree = [chain('child', 20)];
  152. const {result} = reactHooks.renderHook(() =>
  153. useVirtualizedTree({
  154. rowHeight: 10,
  155. scrollContainer: mockScrollContainer,
  156. overscroll: 0,
  157. tree,
  158. renderRow: () => <div />,
  159. })
  160. );
  161. reactHooks.act(() => {
  162. result.current.handleExpandTreeNode(result.current.tree.roots[0], {
  163. expandChildren: true,
  164. });
  165. result.current.dispatch({type: 'set scroll top', payload: 50});
  166. });
  167. const stableKeys = result.current.items.map(item => item.key);
  168. reactHooks.act(() => {
  169. result.current.dispatch({type: 'set scroll top', payload: 60});
  170. });
  171. // First 9 items should be the same, the last item should be different
  172. for (let i = 1; i < stableKeys.length; i++) {
  173. expect(stableKeys[i]).toBe(result.current.items[i - 1].key);
  174. }
  175. // Last item should be different
  176. expect(result.current.items[result.current.items.length - 1].key).toBe(
  177. stableKeys[stableKeys.length - 1] + 1
  178. );
  179. });
  180. });