threadsV2.spec.tsx 37 KB

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