github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/packages/pyroscope-flamegraph/src/FlameGraph/FlameGraphComponent/index.spec.tsx (about) 1 import React from 'react'; 2 import userEvent from '@testing-library/user-event'; 3 import { render, screen, waitFor } from '@testing-library/react'; 4 import { Maybe } from 'true-myth'; 5 import FlamegraphComponent from './index'; 6 import TestData from './testData'; 7 import { BAR_HEIGHT } from './constants'; 8 import { DefaultPalette, FlamegraphPalette } from './colorPalette'; 9 10 // the leaves have already been tested 11 // this is just to guarantee code is compiling 12 // and the callbacks are being called correctly 13 describe('FlamegraphComponent', () => { 14 const setPalette = (p: FlamegraphPalette) => {}; 15 it('renders', () => { 16 const onZoom = jest.fn(); 17 const onReset = jest.fn(); 18 const isDirty = jest.fn(); 19 const onFocusOnNode = jest.fn(); 20 21 render( 22 <FlamegraphComponent 23 updateFitMode={() => ({})} 24 selectedItem={Maybe.nothing()} 25 setActiveItem={() => ({})} 26 showCredit 27 fitMode="HEAD" 28 zoom={Maybe.nothing()} 29 focusedNode={Maybe.nothing()} 30 highlightQuery="" 31 onZoom={onZoom} 32 onFocusOnNode={onFocusOnNode} 33 onReset={onReset} 34 isDirty={isDirty} 35 flamebearer={TestData.SimpleTree} 36 palette={DefaultPalette} 37 setPalette={setPalette} 38 /> 39 ); 40 }); 41 42 it('resizes correctly', () => { 43 const onZoom = jest.fn(); 44 const onReset = jest.fn(); 45 const isDirty = jest.fn(); 46 const onFocusOnNode = jest.fn(); 47 48 render( 49 <FlamegraphComponent 50 updateFitMode={() => ({})} 51 selectedItem={Maybe.nothing()} 52 setActiveItem={() => ({})} 53 showCredit 54 fitMode="HEAD" 55 zoom={Maybe.nothing()} 56 focusedNode={Maybe.nothing()} 57 highlightQuery="" 58 onZoom={onZoom} 59 onFocusOnNode={onFocusOnNode} 60 onReset={onReset} 61 isDirty={isDirty} 62 flamebearer={TestData.SimpleTree} 63 palette={DefaultPalette} 64 setPalette={setPalette} 65 /> 66 ); 67 68 Object.defineProperty(window, 'innerWidth', { 69 writable: true, 70 configurable: true, 71 value: 800, 72 }); 73 74 window.dispatchEvent(new Event('resize')); 75 76 // there's nothing much to assert here 77 expect(window.innerWidth).toBe(800); 78 }); 79 80 it('zooms on click', () => { 81 const onZoom = jest.fn(); 82 const onReset = jest.fn(); 83 const isDirty = jest.fn(); 84 const onFocusOnNode = jest.fn(); 85 86 render( 87 <FlamegraphComponent 88 updateFitMode={() => ({})} 89 selectedItem={Maybe.nothing()} 90 setActiveItem={() => ({})} 91 showCredit 92 fitMode="HEAD" 93 zoom={Maybe.nothing()} 94 focusedNode={Maybe.nothing()} 95 highlightQuery="" 96 onZoom={onZoom} 97 onFocusOnNode={onFocusOnNode} 98 onReset={onReset} 99 isDirty={isDirty} 100 flamebearer={TestData.SimpleTree} 101 palette={DefaultPalette} 102 setPalette={setPalette} 103 /> 104 ); 105 106 userEvent.click(screen.getByTestId('flamegraph-canvas'), { 107 clientX: 0, 108 clientY: BAR_HEIGHT * 3, 109 }); 110 111 expect(onZoom).toHaveBeenCalled(); 112 }); 113 114 describe('context menu', () => { 115 it(`enables "reset view" menuitem when it's dirty`, async () => { 116 const onZoom = jest.fn(); 117 const onReset = jest.fn(); 118 const isDirty = jest.fn(); 119 const onFocusOnNode = jest.fn(); 120 121 const { rerender } = render( 122 <FlamegraphComponent 123 updateFitMode={() => ({})} 124 selectedItem={Maybe.nothing()} 125 setActiveItem={() => ({})} 126 showCredit 127 fitMode="HEAD" 128 zoom={Maybe.nothing()} 129 focusedNode={Maybe.nothing()} 130 highlightQuery="" 131 onZoom={onZoom} 132 onFocusOnNode={onFocusOnNode} 133 onReset={onReset} 134 isDirty={isDirty} 135 flamebearer={TestData.SimpleTree} 136 palette={DefaultPalette} 137 setPalette={setPalette} 138 /> 139 ); 140 141 userEvent.click(screen.getByTestId('flamegraph-canvas'), { 142 button: 2, 143 clientX: 1, 144 clientY: 1, 145 }); 146 147 // should not be available unless we zoom 148 await waitFor(() => 149 expect( 150 screen.queryByRole('menuitem', { name: /Reset View/ }) 151 ).toHaveAttribute('aria-disabled', 'true') 152 ); 153 154 // it's dirty now 155 isDirty.mockReturnValue(true); 156 157 rerender( 158 <FlamegraphComponent 159 updateFitMode={() => ({})} 160 selectedItem={Maybe.nothing()} 161 setActiveItem={() => ({})} 162 showCredit 163 fitMode="HEAD" 164 zoom={Maybe.nothing()} 165 focusedNode={Maybe.nothing()} 166 highlightQuery="" 167 onZoom={onZoom} 168 onFocusOnNode={onFocusOnNode} 169 onReset={onReset} 170 isDirty={isDirty} 171 flamebearer={TestData.SimpleTree} 172 palette={DefaultPalette} 173 setPalette={setPalette} 174 /> 175 ); 176 177 userEvent.click(screen.getByTestId('flamegraph-canvas'), { 178 button: 2, 179 }); 180 181 // should be enabled now 182 expect( 183 screen.queryByRole('menuitem', { name: /Reset View/ }) 184 ).not.toHaveAttribute('aria-disabled', 'true'); 185 }); 186 187 it('triggers a highlight', () => { 188 const onZoom = jest.fn(); 189 const onReset = jest.fn(); 190 const isDirty = jest.fn(); 191 const onFocusOnNode = jest.fn(); 192 193 render( 194 <FlamegraphComponent 195 updateFitMode={() => ({})} 196 selectedItem={Maybe.nothing()} 197 setActiveItem={() => ({})} 198 showCredit 199 fitMode="HEAD" 200 zoom={Maybe.nothing()} 201 focusedNode={Maybe.nothing()} 202 highlightQuery="" 203 onZoom={onZoom} 204 onFocusOnNode={onFocusOnNode} 205 onReset={onReset} 206 isDirty={isDirty} 207 flamebearer={TestData.SimpleTree} 208 palette={DefaultPalette} 209 setPalette={setPalette} 210 /> 211 ); 212 213 // initially the context highlight is not visible 214 expect( 215 screen.getByTestId('flamegraph-highlight-contextmenu') 216 ).not.toBeVisible(); 217 218 // then we click 219 userEvent.click(screen.getByTestId('flamegraph-canvas'), { button: 2 }); 220 221 // should be visible now 222 expect( 223 screen.getByTestId('flamegraph-highlight-contextmenu') 224 ).toBeVisible(); 225 }); 226 }); 227 228 describe('header', () => { 229 const onZoom = jest.fn(); 230 const onReset = jest.fn(); 231 const isDirty = jest.fn(); 232 const onFocusOnNode = jest.fn(); 233 234 it('renders when type is single', () => { 235 render( 236 <FlamegraphComponent 237 updateFitMode={() => ({})} 238 selectedItem={Maybe.nothing()} 239 setActiveItem={() => ({})} 240 showCredit 241 fitMode="HEAD" 242 zoom={Maybe.nothing()} 243 focusedNode={Maybe.nothing()} 244 highlightQuery="" 245 onZoom={onZoom} 246 onFocusOnNode={onFocusOnNode} 247 onReset={onReset} 248 isDirty={isDirty} 249 flamebearer={TestData.SimpleTree} 250 palette={DefaultPalette} 251 setPalette={setPalette} 252 toolbarVisible 253 /> 254 ); 255 256 expect(screen.queryByRole('heading', { level: 2 })).toHaveTextContent( 257 'Frame width represents CPU time per function' 258 ); 259 }); 260 261 it('renders when type is "double"', () => { 262 const flamebearer = TestData.DiffTree; 263 render( 264 <FlamegraphComponent 265 updateFitMode={() => ({})} 266 selectedItem={Maybe.nothing()} 267 setActiveItem={() => ({})} 268 showCredit 269 fitMode="HEAD" 270 zoom={Maybe.nothing()} 271 focusedNode={Maybe.nothing()} 272 highlightQuery="" 273 onZoom={onZoom} 274 onFocusOnNode={onFocusOnNode} 275 onReset={onReset} 276 isDirty={isDirty} 277 flamebearer={flamebearer} 278 palette={DefaultPalette} 279 setPalette={setPalette} 280 toolbarVisible 281 /> 282 ); 283 284 expect(screen.queryByRole('heading', { level: 2 })).toHaveTextContent( 285 '(-) RemovedAdded (+)' 286 ); 287 288 expect(screen.getByTestId('flamegraph-legend')).toBeInTheDocument(); 289 }); 290 }); 291 });