123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414 |
- import {ComponentProps} from 'react';
- import omit from 'lodash/omit';
- import {initializeOrg} from 'sentry-test/initializeOrg';
- import {render, waitFor} from 'sentry-test/reactTestingLibrary';
- import {ApiSource} from 'sentry/components/search/sources/apiSource';
- describe('ApiSource', function () {
- const {org, router} = initializeOrg();
- let orgsMock;
- let projectsMock;
- let teamsMock;
- let membersMock;
- let shortIdMock;
- let eventIdMock;
- const defaultProps: ComponentProps<typeof ApiSource> = {
- query: '',
- organization: org,
- router,
- location: router.location,
- routes: [],
- params: {orgId: org.slug},
- children: jest.fn().mockReturnValue(null),
- };
- beforeEach(function () {
- MockApiClient.clearMockResponses();
- MockApiClient.addMockResponse({
- url: '/organizations/',
- body: [TestStubs.Organization({slug: 'test-org'})],
- });
- orgsMock = MockApiClient.addMockResponse({
- url: '/organizations/',
- body: [TestStubs.Organization({slug: 'foo-org'})],
- });
- projectsMock = MockApiClient.addMockResponse({
- url: '/organizations/org-slug/projects/',
- body: [TestStubs.Project({slug: 'foo-project'})],
- });
- teamsMock = MockApiClient.addMockResponse({
- url: '/organizations/org-slug/teams/',
- body: [TestStubs.Team({slug: 'foo-team'})],
- });
- membersMock = MockApiClient.addMockResponse({
- url: '/organizations/org-slug/members/',
- body: TestStubs.Members(),
- });
- shortIdMock = MockApiClient.addMockResponse({
- url: '/organizations/org-slug/shortids/test-1/',
- body: TestStubs.ShortIdQueryResult(),
- });
- eventIdMock = MockApiClient.addMockResponse({
- url: '/organizations/org-slug/eventids/12345678901234567890123456789012/',
- body: TestStubs.EventIdQueryResult(),
- });
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/plugins/?plugins=_all',
- body: [],
- });
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/plugins/configs/',
- body: [],
- });
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/config/integrations/',
- body: [],
- });
- MockApiClient.addMockResponse({
- url: '/sentry-apps/?status=published',
- body: [],
- });
- MockApiClient.addMockResponse({
- url: '/doc-integrations/',
- body: [],
- });
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/shortids/foo-t/',
- body: [],
- });
- });
- it('queries all API endpoints', function () {
- const mock = jest.fn().mockReturnValue(null);
- render(
- <ApiSource {...defaultProps} query="foo">
- {mock}
- </ApiSource>
- );
- expect(orgsMock).toHaveBeenCalled();
- expect(projectsMock).toHaveBeenCalled();
- expect(teamsMock).toHaveBeenCalled();
- expect(membersMock).toHaveBeenCalled();
- expect(shortIdMock).not.toHaveBeenCalled();
- expect(eventIdMock).not.toHaveBeenCalled();
- });
- it('only queries for shortids when query matches shortid format', async function () {
- const mock = jest.fn().mockReturnValue(null);
- const {rerender} = render(
- <ApiSource {...defaultProps} query="test-">
- {mock}
- </ApiSource>
- );
- expect(shortIdMock).not.toHaveBeenCalled();
- rerender(
- <ApiSource {...defaultProps} query="test-1">
- {mock}
- </ApiSource>
- );
- expect(shortIdMock).toHaveBeenCalled();
- // These may not be desired behavior in future, but lets specify the expectation regardless
- expect(orgsMock).toHaveBeenCalled();
- expect(projectsMock).toHaveBeenCalled();
- expect(teamsMock).toHaveBeenCalled();
- expect(membersMock).toHaveBeenCalled();
- expect(eventIdMock).not.toHaveBeenCalled();
- await waitFor(() => {
- expect(mock).toHaveBeenLastCalledWith(
- expect.objectContaining({
- results: [
- {
- item: expect.objectContaining({
- title: 'group type',
- description: 'group description',
- sourceType: 'issue',
- resultType: 'issue',
- to: '/org-slug/project-slug/issues/1/',
- }),
- score: 1,
- refIndex: 0,
- },
- ],
- })
- );
- });
- });
- it('only queries for eventids when query matches eventid format of 32 chars', async function () {
- const mock = jest.fn().mockReturnValue(null);
- const {rerender} = render(
- <ApiSource {...defaultProps} query="1234567890123456789012345678901">
- {mock}
- </ApiSource>
- );
- expect(eventIdMock).not.toHaveBeenCalled();
- rerender(
- // This is a valid short id now
- <ApiSource {...defaultProps} query="12345678901234567890123456789012">
- {mock}
- </ApiSource>
- );
- await waitFor(() => {
- expect(eventIdMock).toHaveBeenCalled();
- });
- // These may not be desired behavior in future, but lets specify the expectation regardless
- expect(orgsMock).toHaveBeenCalled();
- expect(projectsMock).toHaveBeenCalled();
- expect(teamsMock).toHaveBeenCalled();
- expect(membersMock).toHaveBeenCalled();
- expect(shortIdMock).not.toHaveBeenCalled();
- expect(mock).toHaveBeenLastCalledWith(
- expect.objectContaining({
- results: [
- {
- item: expect.objectContaining({
- title: 'event type',
- description: 'event description',
- sourceType: 'event',
- resultType: 'event',
- to: '/org-slug/project-slug/issues/1/events/12345678901234567890123456789012/',
- }),
- score: 1,
- refIndex: 0,
- },
- ],
- })
- );
- });
- it('only queries org endpoint if there is no org in context', function () {
- const mock = jest.fn().mockReturnValue(null);
- render(
- <ApiSource {...omit(defaultProps, 'organization')} params={{orgId: ''}} query="foo">
- {mock}
- </ApiSource>
- );
- expect(orgsMock).toHaveBeenCalled();
- expect(projectsMock).not.toHaveBeenCalled();
- expect(teamsMock).not.toHaveBeenCalled();
- expect(membersMock).not.toHaveBeenCalled();
- });
- it('render function is called with correct results', async function () {
- const mock = jest.fn().mockReturnValue(null);
- render(
- <ApiSource {...defaultProps} organization={org} query="foo">
- {mock}
- </ApiSource>
- );
- await waitFor(() => {
- expect(mock).toHaveBeenLastCalledWith({
- isLoading: false,
- results: expect.arrayContaining([
- expect.objectContaining({
- item: expect.objectContaining({
- model: expect.objectContaining({
- slug: 'foo-org',
- }),
- sourceType: 'organization',
- resultType: 'settings',
- to: '/settings/foo-org/',
- }),
- matches: expect.anything(),
- score: expect.anything(),
- }),
- expect.objectContaining({
- item: expect.objectContaining({
- model: expect.objectContaining({
- slug: 'foo-org',
- }),
- sourceType: 'organization',
- resultType: 'route',
- to: '/foo-org/',
- }),
- matches: expect.anything(),
- score: expect.anything(),
- }),
- expect.objectContaining({
- item: expect.objectContaining({
- model: expect.objectContaining({
- slug: 'foo-project',
- }),
- sourceType: 'project',
- resultType: 'route',
- to: '/organizations/org-slug/projects/foo-project/?project=2',
- }),
- matches: expect.anything(),
- score: expect.anything(),
- }),
- expect.objectContaining({
- item: expect.objectContaining({
- model: expect.objectContaining({
- slug: 'foo-project',
- }),
- sourceType: 'project',
- resultType: 'route',
- to: '/organizations/org-slug/alerts/rules/?project=2',
- }),
- matches: expect.anything(),
- score: expect.anything(),
- }),
- expect.objectContaining({
- item: expect.objectContaining({
- model: expect.objectContaining({
- slug: 'foo-project',
- }),
- sourceType: 'project',
- resultType: 'settings',
- to: '/settings/org-slug/projects/foo-project/',
- }),
- matches: expect.anything(),
- score: expect.anything(),
- }),
- expect.objectContaining({
- item: expect.objectContaining({
- model: expect.objectContaining({
- slug: 'foo-team',
- }),
- sourceType: 'team',
- resultType: 'settings',
- to: '/settings/org-slug/teams/foo-team/',
- }),
- matches: expect.anything(),
- score: expect.anything(),
- }),
- ]),
- });
- });
- // The return values here are because of fuzzy search matching.
- // There are no members that match
- expect(mock.mock.calls[1][0].results).toHaveLength(6);
- });
- it('render function is called with correct results when API requests partially succeed', async function () {
- const mock = jest.fn().mockReturnValue(null);
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/projects/',
- statusCode: 500,
- });
- render(
- <ApiSource {...defaultProps} query="foo">
- {mock}
- </ApiSource>
- );
- await waitFor(() => {
- expect(mock).toHaveBeenLastCalledWith({
- isLoading: false,
- results: expect.arrayContaining([
- expect.objectContaining({
- item: expect.objectContaining({
- model: expect.objectContaining({
- slug: 'foo-org',
- }),
- }),
- }),
- expect.objectContaining({
- item: expect.objectContaining({
- model: expect.objectContaining({
- slug: 'foo-org',
- }),
- }),
- }),
- expect.objectContaining({
- item: expect.objectContaining({
- model: expect.objectContaining({
- slug: 'foo-team',
- }),
- }),
- }),
- ]),
- });
- });
- // The return values here are because of fuzzy search matching.
- // There are no members that match
- expect(mock.mock.calls[1][0].results).toHaveLength(3);
- });
- it('render function is updated as query changes', async function () {
- const mock = jest.fn().mockReturnValue(null);
- const {rerender} = render(
- <ApiSource {...defaultProps} query="foo">
- {mock}
- </ApiSource>
- );
- await waitFor(() => {
- // The return values here are because of fuzzy search matching.
- // There are no members that match
- expect(mock.mock.calls[1][0].results).toHaveLength(6);
- expect(mock.mock.calls[1][0].results[0].item.model.slug).toBe('foo-org');
- });
- mock.mockClear();
- rerender(
- <ApiSource {...defaultProps} query="foo-t">
- {mock}
- </ApiSource>
- );
- await waitFor(() => {
- // Still have 4 results, but is re-ordered
- expect(mock.mock.calls[0][0].results).toHaveLength(6);
- expect(mock.mock.calls[0][0].results[0].item.model.slug).toBe('foo-team');
- });
- });
- describe('API queries', function () {
- it('calls API based on query string', function () {
- const {rerender} = render(<ApiSource {...defaultProps} query="" />);
- expect(projectsMock).toHaveBeenCalledTimes(1);
- rerender(<ApiSource {...defaultProps} query="f" />);
- // calls API when query string length is 1 char
- expect(projectsMock).toHaveBeenCalledTimes(2);
- rerender(<ApiSource {...defaultProps} query="fo" />);
- // calls API when query string length increases from 1 -> 2
- expect(projectsMock).toHaveBeenCalledTimes(3);
- rerender(<ApiSource {...defaultProps} query="foo" />);
- // Should not query API when query is > 2 chars
- expect(projectsMock).toHaveBeenCalledTimes(3);
- // re-queries API if first 2 characters are different
- rerender(<ApiSource {...defaultProps} query="ba" />);
- expect(projectsMock).toHaveBeenCalledTimes(4);
- // Does not requery when query stays the same
- rerender(<ApiSource {...defaultProps} query="ba" />);
- expect(projectsMock).toHaveBeenCalledTimes(4);
- // queries if we go from 2 chars -> 1 char
- rerender(<ApiSource {...defaultProps} query="b" />);
- expect(projectsMock).toHaveBeenCalledTimes(5);
- });
- });
- });
|