threadsV2.spec.tsx 39 KB

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