import {act, render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
import ProjectActions from 'sentry/actions/projectActions';
import ProjectsStore from 'sentry/stores/projectsStore';
import Projects from 'sentry/utils/projects';
describe('utils.projects', function () {
const renderer = jest.fn(() => null);
const createWrapper = props =>
render(); // eslint-disable-line
beforeEach(function () {
renderer.mockClear();
MockApiClient.clearMockResponses();
act(() =>
ProjectsStore.loadInitialData([
TestStubs.Project({id: '1', slug: 'foo'}),
TestStubs.Project({id: '2', slug: 'bar'}),
])
);
});
afterEach(async function () {
act(() => ProjectsStore.loadInitialData([]));
await tick();
});
describe('with predefined list of slugs', function () {
it('gets projects that are in the ProjectsStore', function () {
createWrapper({slugs: ['foo', 'bar']});
// This is initial state
expect(renderer).toHaveBeenCalledWith(
expect.objectContaining({
fetching: false,
isIncomplete: null,
hasMore: null,
projects: [
expect.objectContaining({
id: '1',
slug: 'foo',
}),
expect.objectContaining({
id: '2',
slug: 'bar',
}),
],
})
);
});
it('fetches projects from API if not found in store', async function () {
const request = MockApiClient.addMockResponse({
url: '/organizations/org-slug/projects/',
query: {
query: 'slug:a slug:b',
},
body: [
TestStubs.Project({
id: '100',
slug: 'a',
}),
TestStubs.Project({
id: '101',
slug: 'b',
}),
],
});
createWrapper({slugs: ['foo', 'a', 'b']});
// This is initial state
expect(renderer).toHaveBeenCalledWith(
expect.objectContaining({
fetching: true,
isIncomplete: null,
hasMore: null,
projects: [
{slug: 'a'},
{slug: 'b'},
expect.objectContaining({
id: '1',
slug: 'foo',
}),
],
})
);
await waitFor(() =>
expect(request).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
query: {
query: 'slug:a slug:b',
collapse: ['latestDeploys'],
},
})
)
);
expect(renderer).toHaveBeenCalledWith(
expect.objectContaining({
fetching: false,
isIncomplete: false,
hasMore: null,
projects: [
expect.objectContaining({
id: '100',
slug: 'a',
}),
expect.objectContaining({
id: '101',
slug: 'b',
}),
expect.objectContaining({
id: '1',
slug: 'foo',
}),
],
})
);
});
it('only has partial results from API', async function () {
const request = MockApiClient.addMockResponse({
url: '/organizations/org-slug/projects/',
body: [
TestStubs.Project({
id: '100',
slug: 'a',
}),
],
});
createWrapper({slugs: ['foo', 'a', 'b']});
// This is initial state
expect(renderer).toHaveBeenCalledWith(
expect.objectContaining({
fetching: true,
isIncomplete: null,
hasMore: null,
projects: [
{slug: 'a'},
{slug: 'b'},
expect.objectContaining({
id: '1',
slug: 'foo',
}),
],
})
);
await waitFor(() =>
expect(request).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
query: {
query: 'slug:a slug:b',
collapse: ['latestDeploys'],
},
})
)
);
expect(renderer).toHaveBeenCalledWith(
expect.objectContaining({
fetching: false,
isIncomplete: true,
hasMore: null,
projects: [
expect.objectContaining({
id: '100',
slug: 'a',
}),
{
slug: 'b',
},
expect.objectContaining({
id: '1',
slug: 'foo',
}),
],
})
);
});
it('responds to updated projects from the project store', async function () {
createWrapper({slugs: ['foo', 'bar']});
await waitFor(() =>
expect(renderer).toHaveBeenCalledWith(
expect.objectContaining({
fetching: false,
isIncomplete: null,
hasMore: null,
projects: [
expect.objectContaining({
id: '1',
slug: 'foo',
}),
expect.objectContaining({
id: '2',
slug: 'bar',
}),
],
})
)
);
const newTeam = TestStubs.Team();
act(() => ProjectActions.addTeamSuccess(newTeam, 'foo'));
await waitFor(() =>
expect(renderer).toHaveBeenCalledWith(
expect.objectContaining({
fetching: false,
isIncomplete: null,
hasMore: null,
projects: [
expect.objectContaining({
id: '1',
slug: 'foo',
teams: [newTeam],
}),
expect.objectContaining({
id: '2',
slug: 'bar',
}),
],
})
)
);
});
});
describe('with predefined list of project ids', function () {
it('gets project ids that are in the ProjectsStore', function () {
createWrapper({projectIds: [1, 2]});
// This is initial state
expect(renderer).toHaveBeenCalledWith(
expect.objectContaining({
fetching: false,
isIncomplete: null,
hasMore: null,
projects: [
expect.objectContaining({
id: '1',
slug: 'foo',
}),
expect.objectContaining({
id: '2',
slug: 'bar',
}),
],
})
);
});
it('fetches projects from API if ids not found in store', async function () {
const request = MockApiClient.addMockResponse({
url: '/organizations/org-slug/projects/',
query: {
all_projects: '1',
collapse: ['latestDeploys'],
},
body: [
TestStubs.Project({
id: '1',
slug: 'foo',
}),
TestStubs.Project({
id: '100',
slug: 'a',
}),
TestStubs.Project({
id: '101',
slug: 'b',
}),
],
});
createWrapper({projectIds: [1, 100, 101]});
// This is initial state
expect(renderer).toHaveBeenCalledWith(
expect.objectContaining({
fetching: true,
isIncomplete: null,
hasMore: null,
projects: [],
})
);
await waitFor(() =>
expect(request).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
query: {
collapse: ['latestDeploys'],
},
})
)
);
expect(renderer).toHaveBeenCalledWith(
expect.objectContaining({
fetching: false,
isIncomplete: null,
hasMore: false,
projects: [
expect.objectContaining({
id: '1',
slug: 'foo',
}),
expect.objectContaining({
id: '100',
slug: 'a',
}),
expect.objectContaining({
id: '101',
slug: 'b',
}),
],
})
);
});
});
describe('with no pre-defined projects', function () {
let request;
beforeEach(async function () {
request = MockApiClient.addMockResponse({
url: '/organizations/org-slug/projects/',
body: [
TestStubs.Project({
id: '100',
slug: 'a',
}),
TestStubs.Project({
id: '101',
slug: 'b',
}),
],
headers: {
Link:
'; rel="previous"; results="true"; cursor="1443575731:0:1", ' +
'; rel="next"; results="true"; cursor="1443575731:0:0',
},
});
act(() => ProjectsStore.loadInitialData([]));
await tick();
});
it('fetches projects from API', async function () {
createWrapper();
// This is initial state
expect(renderer).toHaveBeenCalledWith(
expect.objectContaining({
fetching: true,
isIncomplete: null,
hasMore: null,
projects: [],
})
);
await waitFor(() =>
expect(request).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
query: {
collapse: ['latestDeploys'],
},
})
)
);
expect(renderer).toHaveBeenCalledWith(
expect.objectContaining({
fetching: false,
isIncomplete: null,
hasMore: true,
projects: [
expect.objectContaining({
id: '100',
slug: 'a',
}),
expect.objectContaining({
id: '101',
slug: 'b',
}),
],
})
);
});
it('queries API for more projects and replaces results', async function () {
const myRenderer = jest.fn(({onSearch}) => (
onSearch(target.value)} />
));
createWrapper({children: myRenderer});
// This is initial state
expect(myRenderer).toHaveBeenCalledWith(
expect.objectContaining({
fetching: true,
isIncomplete: null,
hasMore: null,
projects: [],
})
);
request.mockClear();
request = MockApiClient.addMockResponse({
url: '/organizations/org-slug/projects/',
body: [
TestStubs.Project({
id: '102',
slug: 'test1',
}),
TestStubs.Project({
id: '103',
slug: 'test2',
}),
],
});
userEvent.type(screen.getByRole('textbox'), 'test');
expect(request).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
query: {
query: 'test',
collapse: ['latestDeploys'],
},
})
);
await waitFor(() =>
expect(myRenderer).toHaveBeenLastCalledWith(
expect.objectContaining({
fetching: false,
isIncomplete: null,
hasMore: false,
projects: [
expect.objectContaining({
id: '102',
slug: 'test1',
}),
expect.objectContaining({
id: '103',
slug: 'test2',
}),
],
})
)
);
});
it('queries API for more projects and appends results', async function () {
const myRenderer = jest.fn(({onSearch}) => (
onSearch(target.value, {append: true})} />
));
createWrapper({children: myRenderer});
request.mockClear();
request = MockApiClient.addMockResponse({
url: '/organizations/org-slug/projects/',
body: [
TestStubs.Project({
id: '102',
slug: 'test1',
}),
TestStubs.Project({
id: '103',
slug: 'test2',
}),
],
});
userEvent.type(screen.getByRole('textbox'), 'test');
expect(request).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
query: {
query: 'test',
collapse: ['latestDeploys'],
},
})
);
await waitFor(() =>
expect(myRenderer).toHaveBeenLastCalledWith(
expect.objectContaining({
fetching: false,
isIncomplete: null,
hasMore: false,
projects: [
expect.objectContaining({
id: '100',
slug: 'a',
}),
expect.objectContaining({
id: '101',
slug: 'b',
}),
expect.objectContaining({
id: '102',
slug: 'test1',
}),
expect.objectContaining({
id: '103',
slug: 'test2',
}),
],
})
)
);
// Should not have duplicates
userEvent.type(screen.getByRole('textbox'), 'test');
await waitFor(() =>
expect(myRenderer).toHaveBeenLastCalledWith(
expect.objectContaining({
projects: [
expect.objectContaining({
id: '100',
slug: 'a',
}),
expect.objectContaining({
id: '101',
slug: 'b',
}),
expect.objectContaining({
id: '102',
slug: 'test1',
}),
expect.objectContaining({
id: '103',
slug: 'test2',
}),
],
})
)
);
});
});
describe('with all projects prop', function () {
const loadProjects = jest.spyOn(ProjectActions, 'loadProjects');
let mockProjects;
let request;
beforeEach(function () {
mockProjects = [
TestStubs.Project({
id: '100',
slug: 'a',
}),
TestStubs.Project({
id: '101',
slug: 'b',
}),
TestStubs.Project({
id: '102',
slug: 'c',
}),
];
request = MockApiClient.addMockResponse({
url: '/organizations/org-slug/projects/',
query: {
all_projects: '1',
collapse: ['latestDeploys'],
},
body: mockProjects,
});
loadProjects.mockReset();
act(() => ProjectsStore.reset());
});
it('can query for a list of all projects and save it to the store', async function () {
createWrapper({allProjects: true});
// This is initial state
expect(renderer).toHaveBeenCalledWith(
expect.objectContaining({
fetching: true,
isIncomplete: null,
hasMore: null,
projects: [],
})
);
// wait for request to resolve
await waitFor(() =>
expect(request).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
query: {all_projects: 1, collapse: ['latestDeploys']},
})
)
);
expect(renderer).toHaveBeenCalledWith(
expect.objectContaining({
fetching: false,
isIncomplete: null,
hasMore: false,
projects: mockProjects,
})
);
// expect the store action to be called
expect(loadProjects).toHaveBeenCalledWith(mockProjects);
});
it('does not refetch projects that are already loaded in the store', async function () {
act(() => ProjectsStore.loadInitialData(mockProjects));
createWrapper({allProjects: true});
await waitFor(() =>
expect(renderer).toHaveBeenCalledWith(
expect.objectContaining({
fetching: false,
isIncomplete: null,
hasMore: false,
projects: mockProjects,
})
)
);
expect(request).not.toHaveBeenCalled();
expect(loadProjects).not.toHaveBeenCalled();
});
it('responds to updated projects from the project store', async function () {
act(() => ProjectsStore.loadInitialData(mockProjects));
createWrapper({allProjects: true});
await waitFor(() =>
expect(renderer).toHaveBeenCalledWith(
expect.objectContaining({
fetching: false,
isIncomplete: null,
hasMore: false,
projects: mockProjects,
})
)
);
const newTeam = TestStubs.Team();
act(() => ProjectActions.addTeamSuccess(newTeam, 'a'));
// Expect new team information to be available
await waitFor(() =>
expect(renderer).toHaveBeenCalledWith(
expect.objectContaining({
fetching: false,
isIncomplete: null,
hasMore: false,
projects: [
expect.objectContaining({
id: '100',
slug: 'a',
teams: [newTeam],
}),
expect.objectContaining({
id: '101',
slug: 'b',
}),
expect.objectContaining({
id: '102',
slug: 'c',
}),
],
})
)
);
});
});
});