123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- import {CommitFixture} from 'sentry-fixture/commit';
- import {EventFixture} from 'sentry-fixture/event';
- import {GitHubIntegrationFixture} from 'sentry-fixture/githubIntegration';
- import {OrganizationFixture} from 'sentry-fixture/organization';
- import {ProjectFixture} from 'sentry-fixture/project';
- import {ReleaseFixture} from 'sentry-fixture/release';
- import {RepositoryFixture} from 'sentry-fixture/repository';
- import {RepositoryProjectPathConfigFixture} from 'sentry-fixture/repositoryProjectPathConfig';
- import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
- import HookStore from 'sentry/stores/hookStore';
- import ProjectsStore from 'sentry/stores/projectsStore';
- import type {Frame} from 'sentry/types';
- import {CodecovStatusCode} from 'sentry/types';
- import * as analytics from 'sentry/utils/analytics';
- import {StacktraceLink} from './stacktraceLink';
- describe('StacktraceLink', function () {
- const org = OrganizationFixture();
- const platform = 'python';
- const project = ProjectFixture({});
- const event = EventFixture({
- projectID: project.id,
- release: ReleaseFixture({lastCommit: CommitFixture()}),
- platform,
- });
- const integration = GitHubIntegrationFixture();
- const repo = RepositoryFixture({integrationId: integration.id});
- const frame = {filename: '/sentry/app.py', lineNo: 233, inApp: true} as Frame;
- const config = RepositoryProjectPathConfigFixture({project, repo, integration});
- const analyticsSpy = jest.spyOn(analytics, 'trackAnalytics');
- beforeEach(function () {
- jest.clearAllMocks();
- MockApiClient.clearMockResponses();
- ProjectsStore.loadInitialData([project]);
- HookStore.init?.();
- });
- it('renders nothing when missing integrations', async function () {
- MockApiClient.addMockResponse({
- url: `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
- body: {config: null, sourceUrl: null, integrations: []},
- });
- const {container} = render(<StacktraceLink frame={frame} event={event} line="" />);
- await waitFor(() => {
- expect(container).toBeEmptyDOMElement();
- });
- });
- it('renders setup CTA with integration but no configs', async function () {
- MockApiClient.addMockResponse({
- url: `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
- body: {config: null, sourceUrl: null, integrations: [integration]},
- });
- render(<StacktraceLink frame={frame} event={event} line="foo()" />);
- expect(await screen.findByText('Set up Code Mapping')).toBeInTheDocument();
- });
- it('renders source url link', async function () {
- MockApiClient.addMockResponse({
- url: `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
- body: {config, sourceUrl: 'https://something.io', integrations: [integration]},
- });
- render(<StacktraceLink frame={frame} event={event} line="foo()" />);
- const link = await screen.findByRole('link', {name: 'Open this line in GitHub'});
- expect(link).toBeInTheDocument();
- expect(link).toHaveAttribute('href', 'https://something.io#L233');
- });
- it('displays fix modal on error', async function () {
- MockApiClient.addMockResponse({
- url: `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
- body: {
- config,
- sourceUrl: null,
- integrations: [integration],
- },
- });
- render(<StacktraceLink frame={frame} event={event} line="foo()" />);
- expect(
- await screen.findByRole('button', {name: 'Set up Code Mapping'})
- ).toBeInTheDocument();
- });
- it('should hide stacktrace link error state on minified javascript frames', async function () {
- MockApiClient.addMockResponse({
- url: `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
- body: {
- config,
- sourceUrl: null,
- integrations: [integration],
- },
- });
- const {container} = render(
- <StacktraceLink
- frame={frame}
- event={{...event, platform: 'javascript'}}
- line="{snip} somethingInsane=e.IsNotFound {snip}"
- />
- );
- await waitFor(() => {
- expect(container).toBeEmptyDOMElement();
- });
- });
- it('should hide stacktrace link error state on unsupported platforms', async function () {
- MockApiClient.addMockResponse({
- url: `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
- body: {
- config,
- sourceUrl: null,
- integrations: [integration],
- },
- });
- const {container} = render(
- <StacktraceLink frame={frame} event={{...event, platform: 'unreal'}} line="" />
- );
- await waitFor(() => {
- expect(container).toBeEmptyDOMElement();
- });
- });
- it('renders the codecov link', async function () {
- const organization = {
- ...org,
- codecovAccess: true,
- features: ['codecov-integration'],
- };
- MockApiClient.addMockResponse({
- url: `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
- body: {
- config,
- sourceUrl: 'https://github.com/username/path/to/file.py',
- integrations: [integration],
- },
- });
- MockApiClient.addMockResponse({
- url: `/projects/${org.slug}/${project.slug}/stacktrace-coverage/`,
- body: {
- status: CodecovStatusCode.COVERAGE_EXISTS,
- lineCoverage: [[233, 0]],
- coverageUrl: 'https://app.codecov.io/gh/path/to/file.py',
- },
- });
- render(<StacktraceLink frame={frame} event={event} line="foo()" />, {
- organization,
- });
- const link = await screen.findByRole('link', {name: 'Open in Codecov'});
- expect(link).toBeInTheDocument();
- expect(link).toHaveAttribute(
- 'href',
- 'https://app.codecov.io/gh/path/to/file.py#L233'
- );
- await userEvent.click(link);
- expect(analyticsSpy).toHaveBeenCalledWith(
- 'integrations.stacktrace_codecov_link_clicked',
- expect.anything()
- );
- });
- it('renders the missing coverage warning', async function () {
- const organization = {
- ...org,
- codecovAccess: true,
- features: ['codecov-integration'],
- };
- MockApiClient.addMockResponse({
- url: `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
- body: {
- config,
- sourceUrl: 'https://github.com/username/path/to/file.py',
- integrations: [integration],
- },
- });
- MockApiClient.addMockResponse({
- url: `/projects/${org.slug}/${project.slug}/stacktrace-coverage/`,
- body: {status: CodecovStatusCode.NO_COVERAGE_DATA},
- });
- render(<StacktraceLink frame={frame} event={event} line="foo()" />, {
- organization,
- });
- expect(await screen.findByText('Code Coverage not found')).toBeInTheDocument();
- });
- it('renders the link using a valid sourceLink for a .NET project', async function () {
- const dotnetFrame = {
- filename: 'path/to/file.py',
- sourceLink: 'https://www.github.com/username/path/to/file.py#L100',
- lineNo: '100',
- } as unknown as Frame;
- MockApiClient.addMockResponse({
- url: `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
- body: {
- config,
- integrations: [integration],
- },
- });
- render(
- <StacktraceLink
- frame={dotnetFrame}
- event={{...event, platform: 'csharp'}}
- line="foo()"
- />
- );
- const link = await screen.findByRole('link', {name: 'GitHub'});
- expect(link).toBeInTheDocument();
- expect(link).toHaveAttribute(
- 'href',
- 'https://www.github.com/username/path/to/file.py#L100'
- );
- });
- it('hides stacktrace link if there is no source link for .NET projects', async function () {
- MockApiClient.addMockResponse({
- url: `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
- body: {
- config,
- integrations: [integration],
- },
- });
- const {container} = render(
- <StacktraceLink frame={frame} event={{...event, platform: 'csharp'}} line="" />
- );
- await waitFor(() => {
- expect(container).toBeEmptyDOMElement();
- });
- });
- it('renders in-frame stacktrace links and fetches data with 100ms delay', async function () {
- const mockRequest = MockApiClient.addMockResponse({
- url: `/projects/${org.slug}/${project.slug}/stacktrace-link/`,
- body: {config, sourceUrl: 'https://something.io', integrations: [integration]},
- });
- render(<StacktraceLink frame={frame} event={event} line="foo()" />);
- const link = await screen.findByRole('link', {name: 'Open this line in GitHub'});
- expect(link).toBeInTheDocument();
- expect(link).toHaveAttribute('href', 'https://something.io#L233');
- // The link is an icon with aira label
- expect(link).toHaveTextContent('');
- expect(mockRequest).toHaveBeenCalledTimes(1);
- });
- });
|