messageFormatter.spec.tsx 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. import {ReplayConsoleFrameFixture} from 'sentry-fixture/replay/replayBreadcrumbFrameData';
  2. import {ReplayRecordFixture} from 'sentry-fixture/replayRecord';
  3. import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  4. import {BreadcrumbLevelType} from 'sentry/types/breadcrumbs';
  5. import hydrateBreadcrumbs from 'sentry/utils/replays/hydrateBreadcrumbs';
  6. import MessageFormatter from 'sentry/views/replays/detail/console/messageFormatter';
  7. describe('MessageFormatter', () => {
  8. it('Should print console message with placeholders correctly', () => {
  9. const [frame] = hydrateBreadcrumbs(ReplayRecordFixture(), [
  10. ReplayConsoleFrameFixture({
  11. data: {
  12. arguments: ['This is a %s', 'test'],
  13. logger: 'console',
  14. },
  15. level: BreadcrumbLevelType.LOG,
  16. message: 'This is a %s test',
  17. timestamp: new Date('2022-06-22T20:00:39.959Z'),
  18. }),
  19. ]);
  20. render(<MessageFormatter frame={frame} onExpand={() => {}} />);
  21. expect(screen.getByText('This is a test')).toBeInTheDocument();
  22. });
  23. it('Should print console message without data', () => {
  24. const [frame] = hydrateBreadcrumbs(ReplayRecordFixture(), [
  25. ReplayConsoleFrameFixture({
  26. level: BreadcrumbLevelType.LOG,
  27. message: 'This is only a test',
  28. timestamp: new Date('2022-06-22T20:00:39.959Z'),
  29. }),
  30. ]);
  31. // Manually delete `data` from the mock.
  32. // This is reasonable because the type, at this point, `frame` is of type
  33. // `BreadcrumbFrame` and not `ConsoleFrame`.
  34. // When the type is narrowed to `ConsoleFrame` the `data` field is forced to exist.
  35. delete frame.data;
  36. render(<MessageFormatter frame={frame} onExpand={() => {}} />);
  37. expect(screen.getByText('This is only a test')).toBeInTheDocument();
  38. });
  39. it('Should print console message with objects correctly', () => {
  40. const [frame] = hydrateBreadcrumbs(ReplayRecordFixture(), [
  41. ReplayConsoleFrameFixture({
  42. data: {
  43. arguments: ['test', 1, false, {}],
  44. logger: 'console',
  45. },
  46. level: BreadcrumbLevelType.LOG,
  47. message: 'test 1 false [object Object]',
  48. timestamp: new Date('2022-06-22T16:49:11.198Z'),
  49. }),
  50. ]);
  51. const {container} = render(<MessageFormatter frame={frame} onExpand={() => {}} />);
  52. expect(screen.getByText('test 1 false')).toBeInTheDocument();
  53. expect(container).toHaveTextContent('{}');
  54. });
  55. it('Should print console message correctly when it is an Error object', async function () {
  56. const [frame] = hydrateBreadcrumbs(ReplayRecordFixture(), [
  57. ReplayConsoleFrameFixture({
  58. data: {
  59. arguments: [{}],
  60. logger: 'console',
  61. },
  62. level: BreadcrumbLevelType.ERROR,
  63. message: 'Error: this is my error message',
  64. timestamp: new Date('2022-06-22T20:00:39.958Z'),
  65. }),
  66. ]);
  67. render(<MessageFormatter frame={frame} onExpand={() => {}} />);
  68. expect(screen.getByText('1 item')).toBeInTheDocument();
  69. await userEvent.click(screen.getByRole('button', {name: '1 item'}));
  70. expect(screen.getByText('this is my error message')).toBeInTheDocument();
  71. });
  72. it('Should print empty object in case there is no message prop', () => {
  73. const [frame] = hydrateBreadcrumbs(ReplayRecordFixture(), [
  74. ReplayConsoleFrameFixture({
  75. data: {
  76. arguments: [{}],
  77. logger: 'console',
  78. },
  79. level: BreadcrumbLevelType.ERROR,
  80. timestamp: new Date('2022-06-22T20:00:39.958Z'),
  81. }),
  82. ]);
  83. const {container} = render(<MessageFormatter frame={frame} onExpand={() => {}} />);
  84. expect(container).toHaveTextContent('{}');
  85. });
  86. it('Should style "%c" placeholder and print the console message correctly', async function () {
  87. const [frame] = hydrateBreadcrumbs(ReplayRecordFixture(), [
  88. ReplayConsoleFrameFixture({
  89. data: {
  90. arguments: [
  91. '%c prev state',
  92. 'color: #9E9E9E; font-weight: bold; background-image: url(foo);',
  93. {
  94. cart: [],
  95. },
  96. ],
  97. logger: 'console',
  98. },
  99. level: BreadcrumbLevelType.LOG,
  100. message:
  101. '%c prev state color: #9E9E9E; font-weight: bold; background-image: url(foo); [object Object]',
  102. timestamp: new Date('2022-06-09T00:50:25.273Z'),
  103. }),
  104. ]);
  105. const {container} = render(<MessageFormatter frame={frame} onExpand={() => {}} />);
  106. const styledEl = screen.getByText('prev state');
  107. expect(styledEl).toBeInTheDocument();
  108. expect(styledEl).toHaveStyle('color: #9E9E9E;');
  109. expect(styledEl).toHaveStyle('font-weight: bold;');
  110. expect(styledEl).not.toHaveStyle('background-image: url(foo);');
  111. expect(screen.getByText('1 item')).toBeInTheDocument();
  112. await userEvent.click(screen.getByRole('button', {name: '1 item'}));
  113. expect(container).toHaveTextContent('cart: []');
  114. });
  115. it('Should print arrays correctly', async function () {
  116. const [frame] = hydrateBreadcrumbs(ReplayRecordFixture(), [
  117. ReplayConsoleFrameFixture({
  118. data: {
  119. arguments: ['test', ['foo', 'bar']],
  120. logger: 'console',
  121. },
  122. level: BreadcrumbLevelType.LOG,
  123. message: 'test foo,bar',
  124. timestamp: new Date('2022-06-23T17:09:31.158Z'),
  125. }),
  126. ]);
  127. render(<MessageFormatter frame={frame} onExpand={() => {}} />);
  128. expect(screen.getByText('test')).toBeInTheDocument();
  129. expect(screen.getByText('2 items')).toBeInTheDocument();
  130. await userEvent.click(screen.getByRole('button', {name: '2 items'}));
  131. expect(screen.getByText('foo')).toBeInTheDocument();
  132. expect(screen.getByText('bar')).toBeInTheDocument();
  133. });
  134. it('Should print literal %', () => {
  135. const [frame] = hydrateBreadcrumbs(ReplayRecordFixture(), [
  136. ReplayConsoleFrameFixture({
  137. data: {
  138. arguments: ['This is a literal 100%'],
  139. logger: 'console',
  140. },
  141. level: BreadcrumbLevelType.LOG,
  142. message: 'This is a literal 100%',
  143. timestamp: new Date('2022-06-22T20:00:39.959Z'),
  144. }),
  145. ]);
  146. render(<MessageFormatter frame={frame} onExpand={() => {}} />);
  147. expect(screen.getByText('This is a literal 100%')).toBeInTheDocument();
  148. });
  149. it('Should print unbound %s placeholder', () => {
  150. const [frame] = hydrateBreadcrumbs(ReplayRecordFixture(), [
  151. ReplayConsoleFrameFixture({
  152. data: {
  153. arguments: ['Unbound placeholder %s'],
  154. logger: 'console',
  155. },
  156. level: BreadcrumbLevelType.LOG,
  157. message: 'Unbound placeholder %s',
  158. timestamp: new Date('2022-06-22T20:00:39.959Z'),
  159. }),
  160. ]);
  161. render(<MessageFormatter frame={frame} onExpand={() => {}} />);
  162. expect(screen.getByText('Unbound placeholder %s')).toBeInTheDocument();
  163. });
  164. it('Should print placeholder with literal %', () => {
  165. const [frame] = hydrateBreadcrumbs(ReplayRecordFixture(), [
  166. ReplayConsoleFrameFixture({
  167. data: {
  168. arguments: ['Placeholder %s with 100%', 'myPlaceholder'],
  169. logger: 'console',
  170. },
  171. level: BreadcrumbLevelType.LOG,
  172. message: 'Placeholder %s with 100%',
  173. timestamp: new Date('2022-06-22T20:00:39.959Z'),
  174. }),
  175. ]);
  176. render(<MessageFormatter frame={frame} onExpand={() => {}} />);
  177. expect(screen.getByText('Placeholder myPlaceholder with 100%')).toBeInTheDocument();
  178. });
  179. it('should print non-console breadcrumbs', () => {
  180. const [frame] = hydrateBreadcrumbs(ReplayRecordFixture(), [
  181. {
  182. category: 'cypress',
  183. message: 'custom breadcrumb',
  184. timestamp: new Date('2022-06-22T20:00:39.959Z').getTime(),
  185. type: 'info',
  186. },
  187. ]);
  188. render(<MessageFormatter frame={frame} onExpand={() => {}} />);
  189. expect(screen.getByText('cypress custom breadcrumb')).toBeInTheDocument();
  190. });
  191. });