threadsV2.spec.tsx 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031
  1. import {initializeOrg} from 'sentry-test/initializeOrg';
  2. import {render, screen, userEvent, within} from 'sentry-test/reactTestingLibrary';
  3. import ThreadsV2 from 'sentry/components/events/interfaces/threadsV2';
  4. import {displayOptions} from 'sentry/components/events/traceEventDataSection';
  5. import {EventOrGroupType} from 'sentry/types';
  6. import {EntryType, Event} from 'sentry/types/event';
  7. describe('ThreadsV2', function () {
  8. const {project, organization} = initializeOrg();
  9. const org = {...organization, features: ['native-stack-trace-v2']};
  10. describe('non native platform', function () {
  11. describe('other platform', function () {
  12. const event: Event = {
  13. id: '020eb33f6ce64ed6adc60f8993535816',
  14. groupID: '68',
  15. eventID: '020eb33f6ce64ed6adc60f8993535816',
  16. projectID: '2',
  17. size: 3481,
  18. entries: [
  19. {
  20. data: {
  21. values: [
  22. {
  23. type: 'ZeroDivisionError',
  24. value: 'divided by 0',
  25. mechanism: null,
  26. threadId: null,
  27. module: '',
  28. stacktrace: {
  29. frames: [
  30. {
  31. filename: 'puma (3.12.6) lib/puma/thread_pool.rb',
  32. absPath: 'puma (3.12.6) lib/puma/thread_pool.rb',
  33. module: null,
  34. package: null,
  35. platform: null,
  36. instructionAddr: null,
  37. symbolAddr: null,
  38. function: 'block in spawn_thread',
  39. rawFunction: null,
  40. symbol: null,
  41. context: [],
  42. lineNo: 135,
  43. colNo: null,
  44. inApp: false,
  45. trust: null,
  46. errors: null,
  47. vars: null,
  48. },
  49. {
  50. filename: 'puma (3.12.6) lib/puma/server.rb',
  51. absPath: 'puma (3.12.6) lib/puma/server.rb',
  52. module: null,
  53. package: null,
  54. platform: null,
  55. instructionAddr: null,
  56. symbolAddr: null,
  57. function: 'block in run',
  58. rawFunction: null,
  59. symbol: null,
  60. context: [],
  61. lineNo: 334,
  62. colNo: null,
  63. inApp: false,
  64. trust: null,
  65. errors: null,
  66. vars: null,
  67. },
  68. {
  69. filename: 'sentry/controllers/welcome_controller.rb',
  70. absPath: 'sentry/controllers/welcome_controller.rb',
  71. module: null,
  72. package: null,
  73. platform: null,
  74. instructionAddr: null,
  75. symbolAddr: null,
  76. function: 'index',
  77. rawFunction: null,
  78. symbol: null,
  79. context: [
  80. [2, ' before_action :set_sentry_context\n'],
  81. [3, '\n'],
  82. [4, ' def index\n'],
  83. [5, ' 1 / 0\n'],
  84. [6, ' end\n'],
  85. [7, '\n'],
  86. [8, ' def view_error\n'],
  87. ],
  88. lineNo: 5,
  89. colNo: null,
  90. inApp: true,
  91. trust: null,
  92. errors: null,
  93. vars: null,
  94. minGroupingLevel: 1,
  95. },
  96. {
  97. filename: 'sentry/controllers/welcome_controller.rb',
  98. absPath: 'sentry/controllers/welcome_controller.rb',
  99. module: null,
  100. package: null,
  101. platform: null,
  102. instructionAddr: null,
  103. symbolAddr: null,
  104. function: '/',
  105. rawFunction: null,
  106. symbol: null,
  107. context: [
  108. [2, ' before_action :set_sentry_context\n'],
  109. [3, '\n'],
  110. [4, ' def index\n'],
  111. [5, ' 1 / 0\n'],
  112. [6, ' end\n'],
  113. [7, '\n'],
  114. [8, ' def view_error\n'],
  115. ],
  116. lineNo: 5,
  117. colNo: null,
  118. inApp: true,
  119. trust: null,
  120. errors: null,
  121. vars: null,
  122. minGroupingLevel: 0,
  123. },
  124. ],
  125. framesOmitted: null,
  126. registers: null,
  127. hasSystemFrames: true,
  128. },
  129. rawStacktrace: null,
  130. frames: null,
  131. },
  132. ],
  133. hasSystemFrames: true,
  134. excOmitted: null,
  135. },
  136. type: EntryType.EXCEPTION,
  137. },
  138. {
  139. data: {
  140. values: [
  141. {
  142. id: 13920,
  143. current: true,
  144. crashed: true,
  145. name: 'puma 002',
  146. stacktrace: null,
  147. rawStacktrace: null,
  148. },
  149. ],
  150. },
  151. type: EntryType.THREADS,
  152. },
  153. ],
  154. dist: null,
  155. message: '',
  156. title: 'ZeroDivisionError: divided by 0',
  157. location: 'sentry/controllers/welcome_controller.rb',
  158. user: null,
  159. contexts: {},
  160. sdk: null,
  161. context: {},
  162. packages: {},
  163. type: EventOrGroupType.ERROR,
  164. metadata: {
  165. display_title_with_tree_label: false,
  166. filename: 'sentry/controllers/welcome_controller.rb',
  167. finest_tree_label: [
  168. {filebase: 'welcome_controller.rb', function: '/'},
  169. {filebase: 'welcome_controller.rb', function: 'index'},
  170. ],
  171. function: '/',
  172. type: 'ZeroDivisionError',
  173. value: 'divided by 0',
  174. },
  175. tags: [{key: 'level', value: 'error'}],
  176. platform: 'other',
  177. dateReceived: '2021-10-28T12:28:22.318469Z',
  178. errors: [],
  179. crashFile: null,
  180. culprit: 'sentry/controllers/welcome_controller.rb in /',
  181. dateCreated: '2021-10-28T12:28:22.318469Z',
  182. fingerprints: ['58f1f47bea5239ea25397888dc9253d1'],
  183. groupingConfig: {
  184. enhancements: 'eJybzDRxY25-UmZOqpWRgZGhroGJroHRBABbUQb_',
  185. id: 'mobile:2021-02-12',
  186. },
  187. release: null,
  188. userReport: null,
  189. sdkUpdates: [],
  190. nextEventID: null,
  191. previousEventID: null,
  192. };
  193. const props: React.ComponentProps<typeof ThreadsV2> = {
  194. data: event.entries[1].data as React.ComponentProps<typeof ThreadsV2>['data'],
  195. event,
  196. groupingCurrentLevel: 0,
  197. hasHierarchicalGrouping: true,
  198. projectId: project.id,
  199. };
  200. it('renders', function () {
  201. const {container} = render(<ThreadsV2 {...props} />, {
  202. organization: org,
  203. });
  204. // Title
  205. expect(screen.getByRole('heading', {name: 'Stack Trace'})).toBeInTheDocument();
  206. // Actions
  207. expect(
  208. screen.getByRole('button', {name: 'Full Stack Trace'})
  209. ).toBeInTheDocument();
  210. expect(screen.getByRole('button', {name: 'Full Stack Trace'})).not.toHaveClass(
  211. 'active'
  212. );
  213. expect(screen.getByRole('button', {name: 'Options'})).toBeInTheDocument();
  214. // Stack Trace
  215. expect(
  216. screen.getByRole('heading', {name: 'ZeroDivisionError'})
  217. ).toBeInTheDocument();
  218. expect(screen.getByText('divided by 0')).toBeInTheDocument();
  219. expect(screen.getByTestId('stack-trace-content-v2')).toBeInTheDocument();
  220. expect(screen.queryAllByTestId('stack-trace-frame')).toHaveLength(3);
  221. expect(container).toSnapshot();
  222. });
  223. it('toggle full stack trace button', function () {
  224. render(<ThreadsV2 {...props} />, {organization: org});
  225. expect(screen.queryAllByTestId('stack-trace-frame')).toHaveLength(3);
  226. expect(screen.getByRole('button', {name: 'Full Stack Trace'})).not.toHaveClass(
  227. 'active'
  228. );
  229. userEvent.click(screen.getByRole('button', {name: 'Full Stack Trace'}));
  230. expect(screen.getByRole('button', {name: 'Full Stack Trace'})).toHaveClass(
  231. 'active'
  232. );
  233. expect(screen.queryAllByTestId('stack-trace-frame')).toHaveLength(4);
  234. });
  235. it('toggle sort by display option', function () {
  236. render(<ThreadsV2 {...props} />, {organization: org});
  237. expect(
  238. within(screen.getAllByTestId('stack-trace-frame')[0]).getByText(
  239. 'sentry/controllers/welcome_controller.rb'
  240. )
  241. ).toBeInTheDocument();
  242. // Sort by options
  243. expect(screen.getByText('Newest')).toBeInTheDocument();
  244. expect(screen.queryByText('Oldest')).not.toBeInTheDocument();
  245. // Switch to recent last
  246. userEvent.click(screen.getByText('Newest'));
  247. userEvent.click(screen.getByText('Oldest'));
  248. // Recent last is checked
  249. expect(screen.getByText('Oldest')).toBeInTheDocument();
  250. expect(screen.queryByText('Newest')).not.toBeInTheDocument();
  251. // Last frame is the first on the list
  252. expect(
  253. within(screen.getAllByTestId('stack-trace-frame')[0]).getByText(
  254. 'puma (3.12.6) lib/puma/server.rb'
  255. )
  256. ).toBeInTheDocument();
  257. // Click on recent first
  258. userEvent.click(screen.getByText('Oldest'));
  259. userEvent.click(screen.getByText('Newest'));
  260. // First frame is the first on the list
  261. expect(
  262. within(screen.getAllByTestId('stack-trace-frame')[0]).getByText(
  263. 'sentry/controllers/welcome_controller.rb'
  264. )
  265. ).toBeInTheDocument();
  266. });
  267. it('check display options', async function () {
  268. render(<ThreadsV2 {...props} />, {organization: org});
  269. userEvent.click(screen.getByRole('button', {name: 'Options'}));
  270. expect(await screen.findByText('Display')).toBeInTheDocument();
  271. Object.entries(displayOptions).forEach(([key, value]) => {
  272. if (key === 'minified' || key === 'raw-stack-trace') {
  273. expect(screen.getByText(value)).toBeInTheDocument();
  274. return;
  275. }
  276. expect(screen.queryByText(value)).not.toBeInTheDocument();
  277. });
  278. // Hover over the Minified option
  279. userEvent.hover(screen.getByText(displayOptions.minified));
  280. // Minified option is disabled
  281. expect(
  282. await screen.findByText('Minified version not available')
  283. ).toBeInTheDocument();
  284. });
  285. });
  286. });
  287. describe('native platform', function () {
  288. describe('cocoa', function () {
  289. const event: Event = {
  290. id: 'bfe4379d82934b2b91d70b1167bcae8d',
  291. groupID: '24',
  292. eventID: 'bfe4379d82934b2b91d70b1167bcae8d',
  293. projectID: '2',
  294. size: 89101,
  295. entries: [
  296. {
  297. data: {
  298. values: [
  299. {
  300. stacktrace: {
  301. frames: [
  302. {
  303. filename: null,
  304. absPath: null,
  305. module: null,
  306. package:
  307. '/private/var/containers/Bundle/Application/575F9D39-D486-4728-B035-84923A0BE206/sentry-ios-cocoapods.app/Frameworks/Sentry.framework/Sentry',
  308. platform: null,
  309. instructionAddr: '0x1000adb08',
  310. symbolAddr: '0x1000ad5c4',
  311. function:
  312. '__44-[SentryBreadcrumbTracker swizzleSendAction]_block_invoke_2',
  313. rawFunction: null,
  314. symbol: null,
  315. context: [],
  316. lineNo: null,
  317. colNo: null,
  318. inApp: false,
  319. trust: null,
  320. errors: null,
  321. vars: null,
  322. },
  323. {
  324. filename: null,
  325. absPath: null,
  326. module: null,
  327. package: '/System/Library/Frameworks/UIKit.framework/UIKit',
  328. platform: null,
  329. instructionAddr: '0x197885c54',
  330. symbolAddr: '0x197885bf4',
  331. function: '<redacted>',
  332. rawFunction: null,
  333. symbol: null,
  334. context: [],
  335. lineNo: null,
  336. colNo: null,
  337. inApp: false,
  338. trust: null,
  339. errors: null,
  340. vars: null,
  341. },
  342. {
  343. filename: null,
  344. absPath: null,
  345. module: null,
  346. package: '/System/Library/Frameworks/UIKit.framework/UIKit',
  347. platform: null,
  348. instructionAddr: '0x197885c54',
  349. symbolAddr: '0x197885bf4',
  350. function: '<redacted>',
  351. rawFunction: null,
  352. symbol: null,
  353. context: [],
  354. lineNo: null,
  355. colNo: null,
  356. inApp: false,
  357. trust: null,
  358. errors: null,
  359. vars: null,
  360. },
  361. {
  362. filename: null,
  363. absPath: null,
  364. module: null,
  365. package:
  366. '/var/containers/Bundle/Application/575F9D39-D486-4728-B035-84923A0BE206/sentry-ios-cocoapods.app/sentry-ios-cocoapods',
  367. platform: null,
  368. instructionAddr: '0x10008c5ac',
  369. symbolAddr: '0x10008c500',
  370. function: 'ViewController.causeCrash',
  371. rawFunction: 'ViewController.causeCrash(Any) -> ()',
  372. symbol: null,
  373. context: [],
  374. lineNo: null,
  375. colNo: null,
  376. inApp: true,
  377. trust: null,
  378. errors: null,
  379. vars: null,
  380. },
  381. {
  382. filename: null,
  383. absPath: null,
  384. module: null,
  385. package:
  386. '/private/var/containers/Bundle/Application/575F9D39-D486-4728-B035-84923A0BE206/sentry-ios-cocoapods.app/Frameworks/Sentry.framework/Sentry',
  387. platform: null,
  388. instructionAddr: '0x1000b0bfc',
  389. symbolAddr: '0x1000b0be4',
  390. function: '-[SentryClient crash]',
  391. rawFunction: null,
  392. symbol: null,
  393. context: [],
  394. lineNo: null,
  395. colNo: null,
  396. inApp: false,
  397. trust: null,
  398. errors: null,
  399. vars: null,
  400. },
  401. ],
  402. framesOmitted: null,
  403. hasSystemFrames: true,
  404. registers: {
  405. cpsr: '0x60000000',
  406. fp: '0x16fd79870',
  407. lr: '0x10008c5ac',
  408. pc: '0x1000b0bfc',
  409. sp: '0x16fd79810',
  410. x0: '0x1700eee80',
  411. x1: '0x10008e49c',
  412. x10: '0x1b7886ff0',
  413. x11: '0x59160100591680',
  414. x12: '0x0',
  415. x13: '0x591600',
  416. x14: '0x591700',
  417. x15: '0x5916c0',
  418. x16: '0x591601',
  419. x17: '0x1000b0be4',
  420. x18: '0x0',
  421. x19: '0x1740eb200',
  422. x2: '0x0',
  423. x20: '0x10fd08db0',
  424. x21: '0x10008e4de',
  425. x22: '0x10fe0a470',
  426. x23: '0x10fe0a470',
  427. x24: '0x174008ba0',
  428. x25: '0x0',
  429. x26: '0x19838eb61',
  430. x27: '0x1',
  431. x28: '0x170046c60',
  432. x29: '0x16fd79870',
  433. x3: '0x1740eb200',
  434. x4: '0x1740eb200',
  435. x5: '0x1740eb200',
  436. x6: '0x0',
  437. x7: '0x2',
  438. x8: '0x0',
  439. x9: '0x1b7886fec',
  440. },
  441. },
  442. threadId: 0,
  443. module: null,
  444. mechanism: null,
  445. rawStacktrace: null,
  446. value:
  447. 'Attempted to dereference null pointer.\nOriginated at or in a subcall of ViewController.causeCrash(Any) -> ()',
  448. type: 'EXC_BAD_ACCESS',
  449. },
  450. ],
  451. hasSystemFrames: true,
  452. excOmitted: null,
  453. },
  454. type: EntryType.EXCEPTION,
  455. },
  456. {
  457. data: {
  458. values: [
  459. {
  460. id: 0,
  461. current: false,
  462. crashed: true,
  463. name: null,
  464. stacktrace: {
  465. frames: [
  466. {
  467. filename: null,
  468. absPath: null,
  469. module: null,
  470. package: '/System/Library/Frameworks/UIKit.framework/UIKit',
  471. platform: null,
  472. instructionAddr: '0x197885c54',
  473. symbolAddr: '0x197885bf4',
  474. function: '<redacted>',
  475. rawFunction: null,
  476. symbol: null,
  477. context: [],
  478. lineNo: null,
  479. colNo: null,
  480. inApp: false,
  481. trust: null,
  482. errors: null,
  483. vars: null,
  484. },
  485. {
  486. filename: null,
  487. absPath: null,
  488. module: null,
  489. package: '/System/Library/Frameworks/UIKit.framework/UIKit',
  490. platform: null,
  491. instructionAddr: '0x197885c54',
  492. symbolAddr: '0x197885bf4',
  493. function: '<redacted>',
  494. rawFunction: null,
  495. symbol: null,
  496. context: [],
  497. lineNo: null,
  498. colNo: null,
  499. inApp: false,
  500. trust: null,
  501. errors: null,
  502. vars: null,
  503. },
  504. {
  505. filename: null,
  506. absPath: null,
  507. module: null,
  508. package:
  509. '/var/containers/Bundle/Application/575F9D39-D486-4728-B035-84923A0BE206/sentry-ios-cocoapods.app/sentry-ios-cocoapods',
  510. platform: null,
  511. instructionAddr: '0x10008c630',
  512. symbolAddr: '0x10008c5e8',
  513. function: 'ViewController.causeCrash',
  514. rawFunction: '@objc ViewController.causeCrash(Any) -> ()',
  515. symbol: null,
  516. context: [],
  517. lineNo: null,
  518. colNo: null,
  519. inApp: true,
  520. trust: null,
  521. errors: null,
  522. vars: null,
  523. },
  524. {
  525. filename: null,
  526. absPath: null,
  527. module: null,
  528. package:
  529. '/var/containers/Bundle/Application/575F9D39-D486-4728-B035-84923A0BE206/sentry-ios-cocoapods.app/sentry-ios-cocoapods',
  530. platform: null,
  531. instructionAddr: '0x10008c5ac',
  532. symbolAddr: '0x10008c500',
  533. function: 'ViewController.causeCrash',
  534. rawFunction: 'ViewController.causeCrash(Any) -> ()',
  535. symbol: null,
  536. context: [],
  537. lineNo: null,
  538. colNo: null,
  539. inApp: true,
  540. trust: null,
  541. errors: null,
  542. vars: null,
  543. },
  544. {
  545. filename: null,
  546. absPath: null,
  547. module: null,
  548. package:
  549. '/private/var/containers/Bundle/Application/575F9D39-D486-4728-B035-84923A0BE206/sentry-ios-cocoapods.app/Frameworks/Sentry.framework/Sentry',
  550. platform: null,
  551. instructionAddr: '0x1000b0bfc',
  552. symbolAddr: '0x1000b0be4',
  553. function: '-[SentryClient crash]',
  554. rawFunction: null,
  555. symbol: null,
  556. context: [],
  557. lineNo: null,
  558. colNo: null,
  559. inApp: false,
  560. trust: null,
  561. errors: null,
  562. vars: null,
  563. },
  564. ],
  565. framesOmitted: null,
  566. registers: {
  567. cpsr: '0x60000000',
  568. fp: '0x16fd79870',
  569. lr: '0x10008c5ac',
  570. pc: '0x1000b0bfc',
  571. sp: '0x16fd79810',
  572. x0: '0x1700eee80',
  573. x1: '0x10008e49c',
  574. x10: '0x1b7886ff0',
  575. x11: '0x59160100591680',
  576. x12: '0x0',
  577. x13: '0x591600',
  578. x14: '0x591700',
  579. x15: '0x5916c0',
  580. x16: '0x591601',
  581. x17: '0x1000b0be4',
  582. x18: '0x0',
  583. x19: '0x1740eb200',
  584. x2: '0x0',
  585. x20: '0x10fd08db0',
  586. x21: '0x10008e4de',
  587. x22: '0x10fe0a470',
  588. x23: '0x10fe0a470',
  589. x24: '0x174008ba0',
  590. x25: '0x0',
  591. x26: '0x19838eb61',
  592. x27: '0x1',
  593. x28: '0x170046c60',
  594. x29: '0x16fd79870',
  595. x3: '0x1740eb200',
  596. x4: '0x1740eb200',
  597. x5: '0x1740eb200',
  598. x6: '0x0',
  599. x7: '0x2',
  600. x8: '0x0',
  601. x9: '0x1b7886fec',
  602. },
  603. hasSystemFrames: true,
  604. },
  605. rawStacktrace: null,
  606. },
  607. {
  608. id: 1,
  609. current: false,
  610. crashed: false,
  611. name: null,
  612. stacktrace: {
  613. frames: [
  614. {
  615. filename: null,
  616. absPath: null,
  617. module: null,
  618. package: '/usr/lib/system/libsystem_pthread.dylib',
  619. platform: null,
  620. instructionAddr: '0x1907df1a4',
  621. symbolAddr: '0x1907decb8',
  622. function: '_pthread_wqthread',
  623. rawFunction: null,
  624. symbol: null,
  625. context: [],
  626. lineNo: null,
  627. colNo: null,
  628. inApp: false,
  629. trust: null,
  630. errors: null,
  631. vars: null,
  632. },
  633. {
  634. filename: null,
  635. absPath: null,
  636. module: null,
  637. package: '/usr/lib/system/libsystem_kernel.dylib',
  638. platform: null,
  639. instructionAddr: '0x190719a88',
  640. symbolAddr: '0x190719a80',
  641. function: '__workq_kernreturn',
  642. rawFunction: null,
  643. symbol: null,
  644. context: [],
  645. lineNo: null,
  646. colNo: null,
  647. inApp: false,
  648. trust: null,
  649. errors: null,
  650. vars: null,
  651. },
  652. ],
  653. framesOmitted: null,
  654. registers: {
  655. cpsr: '0x0',
  656. fp: '0x16dfcaf70',
  657. lr: '0x1907df1a4',
  658. pc: '0x190719a88',
  659. sp: '0x16dfcaef0',
  660. x0: '0x4',
  661. x1: '0x0',
  662. x10: '0x1',
  663. x11: '0x0',
  664. x12: '0x30000400000d03',
  665. x13: '0x0',
  666. x14: '0x1740e9edc',
  667. x15: '0xfffffff100000000',
  668. x16: '0x170',
  669. x17: '0x191619c10',
  670. x18: '0x0',
  671. x19: '0x16dfcb000',
  672. x2: '0x0',
  673. x20: '0x19',
  674. x21: '0x270019',
  675. x22: '0x0',
  676. x23: '0xd03',
  677. x24: '0x1b788d000',
  678. x25: '0x1b788d000',
  679. x26: '0x0',
  680. x27: '0x80000000',
  681. x28: '0x800010ff',
  682. x29: '0x16dfcaf70',
  683. x3: '0x0',
  684. x4: '0x80010ff',
  685. x5: '0x0',
  686. x6: '0x0',
  687. x7: '0x0',
  688. x8: '0x2',
  689. x9: '0x17409b4ec',
  690. },
  691. hasSystemFrames: false,
  692. },
  693. rawStacktrace: null,
  694. },
  695. ],
  696. },
  697. type: EntryType.THREADS,
  698. },
  699. {
  700. data: {
  701. images: [
  702. {
  703. code_file:
  704. '/var/containers/Bundle/Application/575F9D39-D486-4728-B035-84923A0BE206/sentry-ios-cocoapods.app/sentry-ios-cocoapods',
  705. debug_id: '6b77ffb6-5aba-3b5f-9171-434f9660f738',
  706. image_addr: '0x100084000',
  707. image_size: 49152,
  708. image_vmaddr: '0x100000000',
  709. type: 'macho',
  710. candidates: [],
  711. features: {
  712. has_debug_info: false,
  713. has_sources: false,
  714. has_symbols: false,
  715. has_unwind_info: false,
  716. },
  717. },
  718. {
  719. code_file: '/System/Library/Frameworks/UIKit.framework/UIKit',
  720. debug_id: '314063bd-f85f-321d-88d6-e24a0de464a2',
  721. image_addr: '0x197841000',
  722. image_size: 14315520,
  723. image_vmaddr: '0x187769000',
  724. type: 'macho',
  725. candidates: [],
  726. features: {
  727. has_debug_info: false,
  728. has_sources: false,
  729. has_symbols: false,
  730. has_unwind_info: false,
  731. },
  732. },
  733. {
  734. code_file:
  735. '/private/var/containers/Bundle/Application/575F9D39-D486-4728-B035-84923A0BE206/sentry-ios-cocoapods.app/Frameworks/Sentry.framework/Sentry',
  736. debug_id: '141a9203-b953-3cd6-b9fd-7ba60191a36d',
  737. image_addr: '0x1000a4000',
  738. image_size: 163840,
  739. image_vmaddr: '0x0',
  740. type: 'macho',
  741. candidates: [],
  742. features: {
  743. has_debug_info: false,
  744. has_sources: false,
  745. has_symbols: false,
  746. has_unwind_info: false,
  747. },
  748. },
  749. {
  750. code_file:
  751. '/private/var/containers/Bundle/Application/575F9D39-D486-4728-B035-84923A0BE206/sentry-ios-cocoapods.app/Frameworks/Sentry.framework/Sentry',
  752. debug_id: '141a9203-b953-3cd6-b9fd-7ba60191a36d',
  753. image_addr: '0x1000a4000',
  754. image_size: 163840,
  755. image_vmaddr: '0x0',
  756. type: 'macho',
  757. candidates: [],
  758. features: {
  759. has_debug_info: false,
  760. has_sources: false,
  761. has_symbols: false,
  762. has_unwind_info: false,
  763. },
  764. },
  765. ],
  766. },
  767. type: EntryType.DEBUGMETA,
  768. },
  769. ],
  770. dist: '1',
  771. message: '',
  772. title: 'ViewController.causeCrash | main',
  773. location: null,
  774. user: {
  775. id: '1234',
  776. email: 'hello@sentry.io',
  777. username: null,
  778. ip_address: '172.18.0.1',
  779. name: null,
  780. data: null,
  781. },
  782. type: EventOrGroupType.ERROR,
  783. metadata: {
  784. display_title_with_tree_label: true,
  785. finest_tree_label: [
  786. {
  787. function: 'ViewController.causeCrash',
  788. },
  789. {
  790. function: 'main',
  791. },
  792. ],
  793. function: 'ViewController.causeCrash',
  794. value:
  795. 'Attempted to dereference null pointer.\nOriginated at or in a subcall of ViewController.causeCrash(Any) -> ()',
  796. },
  797. platform: 'cocoa',
  798. dateReceived: '2021-11-02T07:33:38.831104Z',
  799. errors: [
  800. {
  801. type: 'invalid_data',
  802. message: 'Discarded invalid value',
  803. data: {
  804. name: 'threads.values.9.stacktrace.frames',
  805. reason: 'expected a non-empty value',
  806. },
  807. },
  808. ],
  809. crashFile: null,
  810. culprit: '',
  811. dateCreated: '2021-11-02T07:33:38.831104Z',
  812. fingerprints: ['852f6cf1ed76d284b95e7d62275088ca'],
  813. groupingConfig: {
  814. enhancements: 'eJybzDRxY25-UmZOqpWRgZGhroGJroHRBABbUQb_',
  815. id: 'mobile:2021-02-12',
  816. },
  817. tags: [],
  818. contexts: {},
  819. userReport: null,
  820. sdkUpdates: [],
  821. nextEventID: null,
  822. previousEventID: null,
  823. };
  824. const props: React.ComponentProps<typeof ThreadsV2> = {
  825. data: event.entries[1].data as React.ComponentProps<typeof ThreadsV2>['data'],
  826. event,
  827. groupingCurrentLevel: 0,
  828. hasHierarchicalGrouping: true,
  829. projectId: project.id,
  830. };
  831. it('renders', function () {
  832. const {container} = render(<ThreadsV2 {...props} />, {organization: org});
  833. // Title
  834. expect(screen.getByTestId('thread-selector')).toBeInTheDocument();
  835. // Actions
  836. expect(
  837. screen.getByRole('button', {name: 'Full Stack Trace'})
  838. ).toBeInTheDocument();
  839. expect(screen.getByRole('button', {name: 'Full Stack Trace'})).not.toHaveClass(
  840. 'active'
  841. );
  842. expect(screen.getByRole('button', {name: 'Options'})).toBeInTheDocument();
  843. // Stack Trace
  844. expect(screen.getByRole('heading', {name: 'EXC_BAD_ACCESS'})).toBeInTheDocument();
  845. expect(
  846. screen.getByText(
  847. 'Attempted to dereference null pointer. Originated at or in a subcall of ViewController.causeCrash(Any) -> ()'
  848. )
  849. ).toBeInTheDocument();
  850. expect(screen.getByTestId('stack-trace')).toBeInTheDocument();
  851. expect(screen.queryAllByTestId('stack-trace-frame')).toHaveLength(3);
  852. expect(container).toSnapshot();
  853. });
  854. it('toggle full stack trace button', function () {
  855. render(<ThreadsV2 {...props} />, {organization: org});
  856. expect(screen.queryAllByTestId('stack-trace-frame')).toHaveLength(3);
  857. expect(screen.getByRole('button', {name: 'Full Stack Trace'})).not.toHaveClass(
  858. 'active'
  859. );
  860. userEvent.click(screen.getByRole('button', {name: 'Full Stack Trace'}));
  861. expect(screen.getByRole('button', {name: 'Full Stack Trace'})).toHaveClass(
  862. 'active'
  863. );
  864. expect(screen.queryAllByTestId('stack-trace-frame')).toHaveLength(4);
  865. });
  866. it('toggle sort by option', function () {
  867. render(<ThreadsV2 {...props} />, {organization: org});
  868. expect(
  869. within(screen.getAllByTestId('stack-trace-frame')[0]).getByText(
  870. '-[SentryClient crash]'
  871. )
  872. ).toBeInTheDocument();
  873. userEvent.click(screen.getByRole('button', {name: 'Newest'}));
  874. // Sort by options
  875. expect(screen.getAllByText('Newest')).toHaveLength(2);
  876. expect(screen.getByText('Oldest')).toBeInTheDocument();
  877. // Recent first is checked by default
  878. expect(
  879. within(screen.getByTestId('recent-first')).getByTestId('icon-check-mark')
  880. ).toBeInTheDocument();
  881. // Click on recent last
  882. userEvent.click(screen.getByText('Oldest'));
  883. // Recent last is enabled
  884. expect(screen.queryByText('Newest')).not.toBeInTheDocument();
  885. expect(screen.getByText('Oldest')).toBeInTheDocument();
  886. // Last frame is the first on the list
  887. expect(
  888. within(screen.getAllByTestId('stack-trace-frame')[0]).getByText('UIKit')
  889. ).toBeInTheDocument();
  890. // Switch back to recent first
  891. userEvent.click(screen.getByRole('button', {name: 'Oldest'}));
  892. userEvent.click(screen.getByText('Newest'));
  893. // First frame is the first on the list
  894. expect(
  895. within(screen.getAllByTestId('stack-trace-frame')[0]).getByText(
  896. '-[SentryClient crash]'
  897. )
  898. ).toBeInTheDocument();
  899. });
  900. it('check display options', async function () {
  901. render(<ThreadsV2 {...props} />, {organization: org});
  902. userEvent.click(screen.getByRole('button', {name: 'Options'}));
  903. expect(await screen.findByText('Display')).toBeInTheDocument();
  904. Object.values(displayOptions).forEach(value => {
  905. expect(screen.getByText(value)).toBeInTheDocument();
  906. });
  907. // Hover over absolute file paths option
  908. userEvent.hover(screen.getByText(displayOptions['absolute-file-paths']));
  909. // Absolute file paths option is disabled
  910. expect(
  911. await screen.findByText('Absolute file paths not available')
  912. ).toBeInTheDocument();
  913. // Hover over Minified option
  914. userEvent.hover(screen.getByText(displayOptions.minified));
  915. // Minified option is disabled
  916. expect(
  917. await screen.findByText('Unsymbolicated version not available')
  918. ).toBeInTheDocument();
  919. // Function name is not verbose
  920. expect(
  921. within(screen.getAllByTestId('stack-trace-frame')[1]).getByText(
  922. 'ViewController.causeCrash'
  923. )
  924. ).toBeInTheDocument();
  925. // Click on verbose function name option
  926. userEvent.click(screen.getByText(displayOptions['verbose-function-names']));
  927. // Function name is now verbose
  928. expect(
  929. within(screen.getAllByTestId('stack-trace-frame')[1]).getByText(
  930. 'ViewController.causeCrash(Any) -> ()'
  931. )
  932. ).toBeInTheDocument();
  933. // Address is not absolute
  934. expect(
  935. within(screen.getAllByTestId('stack-trace-frame')[1]).getByText('+0x085ac')
  936. ).toBeInTheDocument();
  937. // Click on absolute file paths option
  938. userEvent.click(screen.getByText(displayOptions['absolute-addresses']));
  939. // Address is now absolute
  940. expect(
  941. within(screen.getAllByTestId('stack-trace-frame')[1]).getByText('0x10008c5ac')
  942. ).toBeInTheDocument();
  943. // Click on raw stack trace option
  944. userEvent.click(screen.getByText(displayOptions['raw-stack-trace']));
  945. // Download button is displayed
  946. expect(screen.getByRole('button', {name: 'Download'})).toBeInTheDocument();
  947. // Full stack trace toggler is not displayed
  948. expect(
  949. screen.queryByRole('button', {name: 'Full Stack Trace'})
  950. ).not.toBeInTheDocument();
  951. // Raw content is displayed
  952. expect(screen.queryByRole('list', {name: 'Stack trace'})).not.toBeInTheDocument();
  953. // Raw content and the Raw stack trace option
  954. expect(screen.getAllByTestId('raw-stack-trace')).toHaveLength(2);
  955. });
  956. });
  957. });
  958. });