content.spec.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. import {Event as EventFixture} from 'sentry-fixture/event';
  2. import {EventEntryStacktrace} from 'sentry-fixture/eventEntryStacktrace';
  3. import {EventStacktraceFrame} from 'sentry-fixture/eventStacktraceFrame';
  4. import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  5. import StackTraceContent from 'sentry/components/events/interfaces/crashContent/stackTrace/content';
  6. import {EventOrGroupType} from 'sentry/types';
  7. import {StacktraceType} from 'sentry/types/stacktrace';
  8. const eventEntryStacktrace = EventEntryStacktrace();
  9. const event = EventFixture({
  10. entries: [eventEntryStacktrace],
  11. type: EventOrGroupType.ERROR,
  12. });
  13. const data = eventEntryStacktrace.data as Required<StacktraceType>;
  14. function renderedComponent(
  15. props: Partial<React.ComponentProps<typeof StackTraceContent>>
  16. ) {
  17. return render(
  18. <StackTraceContent
  19. data={data}
  20. className="no-exception"
  21. platform="other"
  22. event={event}
  23. newestFirst
  24. includeSystemFrames
  25. {...props}
  26. />
  27. );
  28. }
  29. describe('StackTrace', function () {
  30. beforeEach(() => {
  31. const promptResponse = {
  32. dismissed_ts: undefined,
  33. snoozed_ts: undefined,
  34. };
  35. MockApiClient.addMockResponse({
  36. url: '/prompts-activity/',
  37. body: promptResponse,
  38. });
  39. });
  40. it('renders', function () {
  41. renderedComponent({});
  42. // stack trace content
  43. const stackTraceContent = screen.getByTestId('stack-trace-content');
  44. expect(stackTraceContent).toBeInTheDocument();
  45. // stack trace content has to have a platform icon and a frame list
  46. expect(stackTraceContent.children).toHaveLength(2);
  47. // platform icon
  48. expect(screen.getByTestId('platform-icon-python')).toBeInTheDocument();
  49. // frame list
  50. const frames = screen.getByTestId('frames');
  51. expect(frames.children).toHaveLength(5);
  52. });
  53. it('renders the frame in the correct order', function () {
  54. renderedComponent({});
  55. // frame - filename
  56. const frameFilenames = screen.getAllByTestId('filename');
  57. expect(frameFilenames).toHaveLength(5);
  58. expect(frameFilenames[0]).toHaveTextContent('raven/scripts/runner.py');
  59. expect(frameFilenames[1]).toHaveTextContent('raven/scripts/runner.py');
  60. expect(frameFilenames[2]).toHaveTextContent('raven/base.py');
  61. expect(frameFilenames[3]).toHaveTextContent('raven/base.py');
  62. expect(frameFilenames[4]).toHaveTextContent('raven/base.py');
  63. // frame - function
  64. const frameFunction = screen.getAllByTestId('function');
  65. expect(frameFunction).toHaveLength(5);
  66. expect(frameFunction[0]).toHaveTextContent('main');
  67. expect(frameFunction[1]).toHaveTextContent('send_test_message');
  68. expect(frameFunction[2]).toHaveTextContent('captureMessage');
  69. expect(frameFunction[3]).toHaveTextContent('capture');
  70. expect(frameFunction[4]).toHaveTextContent('build_msg');
  71. });
  72. it('collapse/expand frames by clicking anywhere in the frame element', async function () {
  73. renderedComponent({});
  74. // frame list
  75. const frames = screen.getByTestId('frames');
  76. expect(frames.children).toHaveLength(5);
  77. // only one frame is expanded by default
  78. expect(screen.getByTestId('toggle-button-expanded')).toBeInTheDocument();
  79. expect(screen.getAllByTestId('toggle-button-collapsed')).toHaveLength(4);
  80. // clickable list item element
  81. const frameTitles = screen.getAllByTestId('title');
  82. // collapse the expanded frame (by default)
  83. await userEvent.click(frameTitles[0]);
  84. // all frames are now collapsed
  85. expect(screen.queryByTestId('toggle-button-expanded')).not.toBeInTheDocument();
  86. expect(screen.getAllByTestId('toggle-button-collapsed')).toHaveLength(5);
  87. // expand penultimate and last frame
  88. await userEvent.click(frameTitles[frameTitles.length - 2]);
  89. await userEvent.click(frameTitles[frameTitles.length - 1]);
  90. // two frames are now collapsed
  91. expect(screen.getAllByTestId('toggle-button-expanded')).toHaveLength(2);
  92. expect(screen.getAllByTestId('toggle-button-collapsed')).toHaveLength(3);
  93. });
  94. it('collapse/expand frames by clicking on the toggle button', async function () {
  95. renderedComponent({});
  96. // frame list
  97. const frames = screen.getByTestId('frames');
  98. expect(frames.children).toHaveLength(5);
  99. const expandedToggleButtons = screen.getByTestId('toggle-button-expanded');
  100. // only one frame is expanded by default
  101. expect(expandedToggleButtons).toBeInTheDocument();
  102. expect(screen.getAllByTestId('toggle-button-collapsed')).toHaveLength(4);
  103. // collapse the expanded frame (by default)
  104. await userEvent.click(expandedToggleButtons);
  105. // all frames are now collapsed
  106. expect(screen.queryByTestId('toggle-button-expanded')).not.toBeInTheDocument();
  107. expect(screen.getAllByTestId('toggle-button-collapsed')).toHaveLength(5);
  108. const collapsedToggleButtons = screen.getAllByTestId('toggle-button-collapsed');
  109. // expand penultimate and last frame
  110. await userEvent.click(collapsedToggleButtons[collapsedToggleButtons.length - 2]);
  111. await userEvent.click(collapsedToggleButtons[collapsedToggleButtons.length - 1]);
  112. // two frames are now collapsed
  113. expect(screen.getAllByTestId('toggle-button-expanded')).toHaveLength(2);
  114. expect(screen.getAllByTestId('toggle-button-collapsed')).toHaveLength(3);
  115. });
  116. it('if all in_app equals false, all the frames are showing by default', function () {
  117. renderedComponent({});
  118. // frame list
  119. const frames = screen.getByTestId('frames');
  120. expect(frames.children).toHaveLength(5);
  121. });
  122. it('if frames are omitted, renders omitted frames', function () {
  123. const newData = {
  124. ...data,
  125. framesOmitted: [0, 3],
  126. };
  127. renderedComponent({
  128. data: newData,
  129. });
  130. const omittedFrames = screen.getByText(
  131. 'Frames 0 until 3 were omitted and not available.'
  132. );
  133. expect(omittedFrames).toBeInTheDocument();
  134. });
  135. it('does not render non in app tags', function () {
  136. const dataFrames = [...data.frames];
  137. dataFrames[0] = {...dataFrames[0], inApp: false};
  138. const newData = {
  139. ...data,
  140. frames: dataFrames,
  141. };
  142. renderedComponent({
  143. data: newData,
  144. });
  145. expect(screen.queryByText('System')).not.toBeInTheDocument();
  146. });
  147. it('displays a toggle button when there is more than one non-inapp frame', function () {
  148. const dataFrames = [...data.frames];
  149. dataFrames[0] = {...dataFrames[0], inApp: true};
  150. const newData = {
  151. ...data,
  152. frames: dataFrames,
  153. };
  154. renderedComponent({
  155. data: newData,
  156. includeSystemFrames: false,
  157. });
  158. expect(screen.getByText('Show 3 more frames')).toBeInTheDocument();
  159. });
  160. it('shows/hides frames when toggle button clicked', async function () {
  161. const dataFrames = [...data.frames];
  162. dataFrames[0] = {...dataFrames[0], inApp: true};
  163. dataFrames[1] = {...dataFrames[1], function: 'non-in-app-frame'};
  164. dataFrames[2] = {...dataFrames[2], function: 'non-in-app-frame'};
  165. dataFrames[3] = {...dataFrames[3], function: 'non-in-app-frame'};
  166. dataFrames[4] = {...dataFrames[4], function: 'non-in-app-frame'};
  167. const newData = {
  168. ...data,
  169. frames: dataFrames,
  170. };
  171. renderedComponent({
  172. data: newData,
  173. includeSystemFrames: false,
  174. });
  175. await userEvent.click(screen.getByText('Show 3 more frames'));
  176. expect(screen.getAllByText('non-in-app-frame')).toHaveLength(4);
  177. await userEvent.click(screen.getByText('Hide 3 more frames'));
  178. expect(screen.getByText('non-in-app-frame')).toBeInTheDocument();
  179. });
  180. it('does not display a toggle button when there is only one non-inapp frame', function () {
  181. const dataFrames = [...data.frames];
  182. dataFrames[0] = {...dataFrames[0], inApp: true};
  183. dataFrames[2] = {...dataFrames[2], inApp: true};
  184. dataFrames[4] = {...dataFrames[4], inApp: true};
  185. const newData = {
  186. ...data,
  187. frames: dataFrames,
  188. };
  189. renderedComponent({
  190. data: newData,
  191. includeSystemFrames: false,
  192. });
  193. expect(screen.queryByText(/Show .* more frames*/)).not.toBeInTheDocument();
  194. });
  195. describe('if there is a frame with in_app equal to true, display only in_app frames', function () {
  196. it('displays crashed from only', function () {
  197. const dataFrames = [...data.frames];
  198. const newData = {
  199. ...data,
  200. hasSystemFrames: true,
  201. frames: [
  202. {...dataFrames[0], inApp: true},
  203. ...dataFrames.splice(1, dataFrames.length),
  204. ],
  205. };
  206. renderedComponent({
  207. data: newData,
  208. event: EventFixture({
  209. ...event,
  210. entries: [{...event.entries[0], stacktace: newData.frames}],
  211. }),
  212. includeSystemFrames: false,
  213. });
  214. // clickable list item element
  215. const frameTitles = screen.getAllByTestId('title');
  216. // frame list - in app only
  217. expect(frameTitles).toHaveLength(2);
  218. expect(frameTitles[0]).toHaveTextContent(
  219. 'Crashed in non-app: raven/scripts/runner.py in main at line 112'
  220. );
  221. expect(frameTitles[1]).toHaveTextContent('raven/base.py in build_msg at line 303');
  222. });
  223. it('displays called from only', function () {
  224. const dataFrames = [...data.frames];
  225. const newData = {
  226. ...data,
  227. hasSystemFrames: true,
  228. registers: {},
  229. frames: [
  230. ...dataFrames.splice(0, dataFrames.length - 1),
  231. {...dataFrames[dataFrames.length - 1], inApp: true},
  232. ],
  233. };
  234. renderedComponent({
  235. data: newData,
  236. event: EventFixture({
  237. ...event,
  238. entries: [{...event.entries[0], stacktrace: newData.frames}],
  239. }),
  240. includeSystemFrames: false,
  241. });
  242. // clickable list item element
  243. const frameTitles = screen.getAllByTestId('title');
  244. // frame list - in app only
  245. expect(frameTitles).toHaveLength(2);
  246. expect(frameTitles[0]).toHaveTextContent(
  247. 'raven/scripts/runner.py in main at line 112'
  248. );
  249. expect(frameTitles[1]).toHaveTextContent(
  250. 'Called from: raven/scripts/runner.py in send_test_message at line 77'
  251. );
  252. });
  253. it('displays crashed from and called from', function () {
  254. const dataFrames = [...data.frames];
  255. const newData = {
  256. ...data,
  257. hasSystemFrames: true,
  258. frames: [
  259. ...dataFrames.slice(0, 1),
  260. {...dataFrames[1], inApp: true},
  261. ...dataFrames.slice(2, dataFrames.length),
  262. ],
  263. };
  264. renderedComponent({
  265. data: newData,
  266. event: EventFixture({
  267. ...event,
  268. entries: [{...event.entries[0], stacktrace: newData.frames}],
  269. }),
  270. includeSystemFrames: false,
  271. });
  272. // clickable list item element
  273. const frameTitles = screen.getAllByTestId('title');
  274. // frame list - in app only
  275. expect(frameTitles).toHaveLength(3);
  276. expect(frameTitles[0]).toHaveTextContent(
  277. 'Crashed in non-app: raven/scripts/runner.py in main at line 112'
  278. );
  279. expect(frameTitles[1]).toHaveTextContent('raven/base.py in capture at line 459');
  280. expect(frameTitles[2]).toHaveTextContent(
  281. 'Called from: raven/base.py in build_msg at line 303'
  282. );
  283. });
  284. it('displays "occurred in" when event is not an error', function () {
  285. const dataFrames = [...data.frames];
  286. const newData = {
  287. ...data,
  288. hasSystemFrames: true,
  289. frames: [
  290. {...dataFrames[0], inApp: true},
  291. ...dataFrames.splice(1, dataFrames.length),
  292. ],
  293. };
  294. renderedComponent({
  295. data: newData,
  296. event: EventFixture({
  297. ...event,
  298. entries: [{...event.entries[0], stacktrace: newData.frames}],
  299. type: EventOrGroupType.TRANSACTION,
  300. }),
  301. includeSystemFrames: false,
  302. });
  303. // clickable list item element
  304. const frameTitles = screen.getAllByTestId('title');
  305. // frame list - in app only
  306. expect(frameTitles).toHaveLength(2);
  307. expect(frameTitles[0]).toHaveTextContent(
  308. 'Occurred in non-app: raven/scripts/runner.py in main at line 112'
  309. );
  310. expect(frameTitles[1]).toHaveTextContent('raven/base.py in build_msg at line 303');
  311. });
  312. it('displays "occurred in" when event is an ANR error', function () {
  313. const dataFrames = [...data.frames];
  314. const newData = {
  315. ...data,
  316. hasSystemFrames: true,
  317. frames: [
  318. {...dataFrames[0], inApp: true},
  319. ...dataFrames.splice(1, dataFrames.length),
  320. ],
  321. };
  322. renderedComponent({
  323. data: newData,
  324. event: EventFixture({
  325. ...event,
  326. entries: [{...event.entries[0], stacktrace: newData.frames}],
  327. type: EventOrGroupType.ERROR,
  328. tags: [{key: 'mechanism', value: 'ANR'}],
  329. }),
  330. includeSystemFrames: false,
  331. });
  332. // clickable list item element
  333. const frameTitles = screen.getAllByTestId('title');
  334. // frame list - in app only
  335. expect(frameTitles).toHaveLength(2);
  336. expect(frameTitles[0]).toHaveTextContent(
  337. 'Occurred in non-app: raven/scripts/runner.py in main at line 112'
  338. );
  339. expect(frameTitles[1]).toHaveTextContent('raven/base.py in build_msg at line 303');
  340. });
  341. });
  342. describe('platform icons', function () {
  343. it('uses the top in-app frame file extension for mixed stack trace platforms', function () {
  344. renderedComponent({
  345. data: {
  346. ...data,
  347. frames: [
  348. EventStacktraceFrame({
  349. inApp: true,
  350. filename: 'foo.cs',
  351. }),
  352. EventStacktraceFrame({
  353. inApp: true,
  354. filename: 'foo.py',
  355. }),
  356. EventStacktraceFrame({
  357. inApp: true,
  358. filename: 'foo',
  359. }),
  360. EventStacktraceFrame({
  361. inApp: false,
  362. filename: 'foo.rb',
  363. }),
  364. ],
  365. },
  366. });
  367. // foo.py is the most recent in-app frame with a valid file extension
  368. expect(screen.getByTestId('platform-icon-python')).toBeInTheDocument();
  369. });
  370. it('uses frame.platform if file extension does not work', function () {
  371. renderedComponent({
  372. data: {
  373. ...data,
  374. frames: [
  375. EventStacktraceFrame({
  376. inApp: true,
  377. filename: 'foo.cs',
  378. }),
  379. EventStacktraceFrame({
  380. inApp: true,
  381. filename: 'foo',
  382. platform: 'node',
  383. }),
  384. EventStacktraceFrame({
  385. inApp: true,
  386. filename: 'foo',
  387. }),
  388. EventStacktraceFrame({
  389. inApp: false,
  390. filename: 'foo.rb',
  391. }),
  392. ],
  393. },
  394. });
  395. expect(screen.getByTestId('platform-icon-node')).toBeInTheDocument();
  396. });
  397. it('falls back to the event platform if there is no other information', function () {
  398. renderedComponent({
  399. data: {
  400. ...data,
  401. frames: [
  402. EventStacktraceFrame({
  403. inApp: true,
  404. filename: 'foo',
  405. platform: null,
  406. }),
  407. ],
  408. },
  409. platform: 'python',
  410. });
  411. expect(screen.getByTestId('platform-icon-python')).toBeInTheDocument();
  412. });
  413. });
  414. });