useVirtualizedTree.spec.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  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(useVirtualizedTree, {
  41. initialProps: {
  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('expands tree', () => {
  52. const mockScrollContainer = makeScrollContainerMock({height: 100});
  53. const tree = [chain('child', 5)];
  54. const {result} = reactHooks.renderHook(useVirtualizedTree, {
  55. initialProps: {
  56. expanded: true,
  57. rowHeight: 10,
  58. scrollContainer: mockScrollContainer,
  59. overscroll: 0,
  60. tree,
  61. renderRow: () => <div />,
  62. },
  63. });
  64. expect(result.current.items.length).toBe(5);
  65. });
  66. it('shows first 10 items', () => {
  67. const mockScrollContainer = makeScrollContainerMock({height: 100});
  68. const tree = [chain('child', 10)];
  69. const {result} = reactHooks.renderHook(useVirtualizedTree, {
  70. initialProps: {
  71. rowHeight: 10,
  72. scrollContainer: mockScrollContainer,
  73. overscroll: 0,
  74. tree,
  75. renderRow: () => <div />,
  76. },
  77. });
  78. reactHooks.act(() => {
  79. result.current.handleExpandTreeNode(result.current.tree.roots[0], {
  80. expandChildren: true,
  81. });
  82. });
  83. for (let i = 0; i < 10; i++) {
  84. expect(result.current.items[i].item.node.id).toEqual(`child-${i}`);
  85. }
  86. expect(result.current.items.length).toBe(10);
  87. });
  88. it('shows 5-15 items', async () => {
  89. const mockScrollContainer = makeScrollContainerMock({height: 100});
  90. const tree = [chain('child', 20)];
  91. const {result} = reactHooks.renderHook(useVirtualizedTree, {
  92. initialProps: {
  93. rowHeight: 10,
  94. scrollContainer: mockScrollContainer,
  95. overscroll: 0,
  96. tree,
  97. renderRow: () => <div />,
  98. },
  99. });
  100. reactHooks.act(() => {
  101. result.current.handleExpandTreeNode(result.current.tree.roots[0], {
  102. expandChildren: true,
  103. });
  104. result.current.dispatch({type: 'set scroll top', payload: 50});
  105. });
  106. await waitFor(() => {
  107. expect(result.current.items.length).toBe(10);
  108. });
  109. for (let i = 0; i < 10; i++) {
  110. expect(result.current.items[i].item.node.id).toEqual(`child-${i + 5}`);
  111. }
  112. expect(result.current.items.length).toBe(10);
  113. });
  114. it('shows last 10 items', async () => {
  115. const mockScrollContainer = makeScrollContainerMock({height: 100});
  116. const tree = [chain('child', 20)];
  117. const {result} = reactHooks.renderHook(useVirtualizedTree, {
  118. initialProps: {
  119. rowHeight: 10,
  120. scrollContainer: mockScrollContainer,
  121. overscroll: 0,
  122. tree,
  123. renderRow: () => <div />,
  124. },
  125. });
  126. reactHooks.act(() => {
  127. result.current.handleExpandTreeNode(result.current.tree.roots[0], {
  128. expandChildren: true,
  129. });
  130. result.current.dispatch({type: 'set scroll top', payload: 100});
  131. });
  132. await waitFor(() => {
  133. expect(result.current.items.length).toBe(10);
  134. });
  135. for (let i = 0; i < 10; i++) {
  136. expect(result.current.items[i].item.node.id).toEqual(`child-${i + 10}`);
  137. }
  138. expect(result.current.items.length).toBe(10);
  139. });
  140. it('shows overscroll items', () => {
  141. const mockScrollContainer = makeScrollContainerMock({height: 100});
  142. const tree = [chain('child', 20)];
  143. const {result} = reactHooks.renderHook(useVirtualizedTree, {
  144. initialProps: {
  145. rowHeight: 10,
  146. scrollContainer: mockScrollContainer,
  147. overscroll: 2,
  148. tree,
  149. renderRow: () => <div />,
  150. },
  151. });
  152. reactHooks.act(() => {
  153. result.current.handleExpandTreeNode(result.current.tree.roots[0], {
  154. expandChildren: true,
  155. });
  156. result.current.dispatch({type: 'set scroll top', payload: 50});
  157. });
  158. for (let i = 3; i < 17; i++) {
  159. // Should display nodes 5-15, but since we use overscroll, it should display nodes 3-17
  160. expect(result.current.items[i - 3].item.node.id).toEqual(`child-${i}`);
  161. }
  162. expect(result.current.items.length).toBe(14);
  163. });
  164. it('items have a stable key', () => {
  165. const mockScrollContainer = makeScrollContainerMock({height: 100});
  166. const tree = [chain('child', 20)];
  167. const {result} = reactHooks.renderHook(useVirtualizedTree, {
  168. initialProps: {
  169. rowHeight: 10,
  170. scrollContainer: mockScrollContainer,
  171. overscroll: 0,
  172. tree,
  173. renderRow: () => <div />,
  174. },
  175. });
  176. reactHooks.act(() => {
  177. result.current.handleExpandTreeNode(result.current.tree.roots[0], {
  178. expandChildren: true,
  179. });
  180. result.current.dispatch({type: 'set scroll top', payload: 50});
  181. });
  182. const stableKeys = result.current.items.map(item => item.key);
  183. reactHooks.act(() => {
  184. result.current.dispatch({type: 'set scroll top', payload: 60});
  185. });
  186. // First 9 items should be the same, the last item should be different
  187. for (let i = 1; i < stableKeys.length; i++) {
  188. expect(stableKeys[i]).toBe(result.current.items[i - 1].key);
  189. }
  190. // Last item should be different
  191. expect(result.current.items[result.current.items.length - 1].key).toBe(
  192. stableKeys[stableKeys.length - 1] + 1
  193. );
  194. });
  195. });