import {ReplayConsoleFrameFixture} from 'sentry-fixture/replay/replayBreadcrumbFrameData'; import {ReplayRecordFixture} from 'sentry-fixture/replayRecord'; import {render, screen} from 'sentry-test/reactTestingLibrary'; import {BreadcrumbLevelType} from 'sentry/types/breadcrumbs'; import hydrateBreadcrumbs from 'sentry/utils/replays/hydrateBreadcrumbs'; import MessageFormatter from 'sentry/views/replays/detail/console/messageFormatter'; const mockRRWebFrames = []; // This is only needed for replay.hydrate-error breadcrumbs. describe('MessageFormatter', () => { it('Should print console message with placeholders correctly', () => { const [frame] = hydrateBreadcrumbs( ReplayRecordFixture(), [ ReplayConsoleFrameFixture({ data: { arguments: ['This is a %s', 'test'], logger: 'console', }, level: BreadcrumbLevelType.LOG, message: 'This is a %s test', timestamp: new Date('2022-06-22T20:00:39.959Z'), }), ], mockRRWebFrames ); render(); expect(screen.getByText('This is a test')).toBeInTheDocument(); }); it('Should print console message without data', () => { const [frame] = hydrateBreadcrumbs( ReplayRecordFixture(), [ ReplayConsoleFrameFixture({ level: BreadcrumbLevelType.LOG, message: 'This is only a test', timestamp: new Date('2022-06-22T20:00:39.959Z'), }), ], mockRRWebFrames ); // Manually delete `data` from the mock. // This is reasonable because the type, at this point, `frame` is of type // `BreadcrumbFrame` and not `ConsoleFrame`. // When the type is narrowed to `ConsoleFrame` the `data` field is forced to exist. delete frame.data; render(); expect(screen.getByText('This is only a test')).toBeInTheDocument(); }); it('Should print console message with objects correctly', () => { const [frame] = hydrateBreadcrumbs( ReplayRecordFixture(), [ ReplayConsoleFrameFixture({ data: { arguments: ['test', 1, false, {}], logger: 'console', }, level: BreadcrumbLevelType.LOG, message: 'test 1 false [object Object]', timestamp: new Date('2022-06-22T16:49:11.198Z'), }), ], mockRRWebFrames ); render(); expect(screen.getByText('test 1 false')).toBeInTheDocument(); expect(screen.getByText('{}')).toBeInTheDocument(); }); it('Should print console message correctly when it is an Error object', () => { const [frame] = hydrateBreadcrumbs( ReplayRecordFixture(), [ ReplayConsoleFrameFixture({ data: { arguments: [{}], logger: 'console', }, level: BreadcrumbLevelType.ERROR, message: 'Error: this is my error message', timestamp: new Date('2022-06-22T20:00:39.958Z'), }), ], mockRRWebFrames ); render(); expect(screen.getByText('this is my error message')).toBeInTheDocument(); }); it('Should print empty object in case there is no message prop', () => { const [frame] = hydrateBreadcrumbs( ReplayRecordFixture(), [ ReplayConsoleFrameFixture({ data: { arguments: [{}], logger: 'console', }, level: BreadcrumbLevelType.ERROR, timestamp: new Date('2022-06-22T20:00:39.958Z'), }), ], mockRRWebFrames ); render(); expect(screen.getByText('{}')).toBeInTheDocument(); }); it('Should style "%c" placeholder and print the console message correctly', () => { const [frame] = hydrateBreadcrumbs( ReplayRecordFixture(), [ ReplayConsoleFrameFixture({ data: { arguments: [ '%c prev state', 'color: #9E9E9E; font-weight: bold; background-image: url(foo);', { cart: [], }, ], logger: 'console', }, level: BreadcrumbLevelType.LOG, message: '%c prev state color: #9E9E9E; font-weight: bold; background-image: url(foo); [object Object]', timestamp: new Date('2022-06-09T00:50:25.273Z'), }), ], mockRRWebFrames ); render(); const styledEl = screen.getByText('prev state'); expect(styledEl).toBeInTheDocument(); expect(styledEl).toHaveStyle('color: #9E9E9E;'); expect(styledEl).toHaveStyle('font-weight: bold;'); expect(styledEl).not.toHaveStyle('background-image: url(foo);'); expect(screen.getByText('cart')).toBeInTheDocument(); expect(screen.getByText('Array(0)')).toBeInTheDocument(); }); it('Should print arrays correctly', () => { const [frame] = hydrateBreadcrumbs( ReplayRecordFixture(), [ ReplayConsoleFrameFixture({ data: { arguments: ['test', ['foo', 'bar']], logger: 'console', }, level: BreadcrumbLevelType.LOG, message: 'test foo,bar', timestamp: new Date('2022-06-23T17:09:31.158Z'), }), ], mockRRWebFrames ); render(); expect(screen.getByText('test')).toBeInTheDocument(); expect(screen.getByText('(2)')).toBeInTheDocument(); // expect(screen.getByText('[')).toBeInTheDocument(); expect(screen.getByText('"foo"')).toBeInTheDocument(); expect(screen.getByText('"bar"')).toBeInTheDocument(); // expect(screen.getByText(']')).toBeInTheDocument(); }); it('Should print literal %', () => { const [frame] = hydrateBreadcrumbs( ReplayRecordFixture(), [ ReplayConsoleFrameFixture({ data: { arguments: ['This is a literal 100%'], logger: 'console', }, level: BreadcrumbLevelType.LOG, message: 'This is a literal 100%', timestamp: new Date('2022-06-22T20:00:39.959Z'), }), ], mockRRWebFrames ); render(); expect(screen.getByText('This is a literal 100%')).toBeInTheDocument(); }); it('Should print unbound %s placeholder', () => { const [frame] = hydrateBreadcrumbs( ReplayRecordFixture(), [ ReplayConsoleFrameFixture({ data: { arguments: ['Unbound placeholder %s'], logger: 'console', }, level: BreadcrumbLevelType.LOG, message: 'Unbound placeholder %s', timestamp: new Date('2022-06-22T20:00:39.959Z'), }), ], mockRRWebFrames ); render(); expect(screen.getByText('Unbound placeholder %s')).toBeInTheDocument(); }); it('Should print placeholder with literal %', () => { const [frame] = hydrateBreadcrumbs( ReplayRecordFixture(), [ ReplayConsoleFrameFixture({ data: { arguments: ['Placeholder %s with 100%', 'myPlaceholder'], logger: 'console', }, level: BreadcrumbLevelType.LOG, message: 'Placeholder %s with 100%', timestamp: new Date('2022-06-22T20:00:39.959Z'), }), ], mockRRWebFrames ); render(); expect(screen.getByText('Placeholder myPlaceholder with 100%')).toBeInTheDocument(); }); });