import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
import ProjectSelector from 'sentry/components/organizations/projectSelector';
describe('ProjectSelector', function () {
const testProject = TestStubs.Project({
id: '1',
slug: 'test-project',
isBookmarked: true,
isMember: true,
});
const anotherProject = TestStubs.Project({
id: '2',
slug: 'another-project',
isMember: true,
});
const mockOrg = TestStubs.Organization({
id: '1',
slug: 'org',
features: ['new-teams', 'global-views'],
access: [],
});
const routerContext = TestStubs.routerContext([{organization: mockOrg}]);
function openMenu() {
userEvent.click(screen.getByRole('button'));
}
function applyMenu() {
userEvent.click(screen.getByRole('button', {name: 'Apply Filter'}));
}
const props = {
isGlobalSelectionReady: true,
organization: mockOrg,
memberProjects: [testProject, anotherProject],
nonMemberProjects: [],
value: [],
onApplyChange: () => {},
onChange: () => {},
menuFooter: () => {},
};
it('should show empty message with no projects button, when no projects, and has no "project:write" access', function () {
render(
,
{context: routerContext}
);
openMenu();
expect(screen.getByText('You have no projects')).toBeInTheDocument();
// Should not have "Create Project" button
const createProject = screen.getByLabelText('Add Project');
expect(createProject).toBeInTheDocument();
expect(createProject).toBeDisabled();
});
it('should show empty message and create project button, when no projects and has "project:write" access', function () {
render(
,
{context: routerContext}
);
openMenu();
expect(screen.getByText('You have no projects')).toBeInTheDocument();
// Should not have "Create Project" button
const createProject = screen.getByLabelText('Add Project');
expect(createProject).toBeInTheDocument();
expect(createProject).toBeEnabled();
});
it('does not open selector menu when disabled', function () {
render(, {context: routerContext});
openMenu();
expect(screen.queryByRole('checkbox')).not.toBeInTheDocument();
});
it('lists projects and has filter', function () {
render(, {context: routerContext});
openMenu();
expect(screen.getByText(testProject.slug)).toBeInTheDocument();
expect(screen.getByText(anotherProject.slug)).toBeInTheDocument();
});
it('can filter projects by project name', function () {
render(, {context: routerContext});
openMenu();
screen.getByRole('textbox').focus();
userEvent.keyboard('TEST');
const item = screen.getByTestId('badge-display-name');
expect(item).toBeInTheDocument();
expect(item).toHaveTextContent(testProject.slug);
});
it('shows empty filter message when filtering has no results', function () {
render(, {context: routerContext});
openMenu();
screen.getByRole('textbox').focus();
userEvent.keyboard('Foo');
expect(screen.queryByTestId('badge-display-name')).not.toBeInTheDocument();
expect(screen.getByText('No projects found')).toBeInTheDocument();
});
it('does not close dropdown when input is clicked', function () {
render(, {context: routerContext});
openMenu();
userEvent.click(screen.getByRole('textbox'));
// Dropdown is still open
expect(screen.getByText(testProject.slug)).toBeInTheDocument();
});
it('closes dropdown when project is selected', function () {
render(, {context: routerContext});
openMenu();
userEvent.click(screen.getByText(testProject.slug));
// Dropdown is closed
expect(screen.queryByText(testProject.slug)).not.toBeInTheDocument();
});
it('calls callback when project is selected', function () {
const onApplyChangeMock = jest.fn();
render(, {
context: routerContext,
});
openMenu();
// Select first project
userEvent.click(screen.getByText(testProject.slug));
expect(onApplyChangeMock).toHaveBeenCalledWith([parseInt(testProject.id, 10)]);
});
it('does not call `onUpdate` when using multi select', function () {
const onChangeMock = jest.fn();
const onApplyChangeMock = jest.fn();
render(
,
{context: routerContext}
);
openMenu();
// Check the first project
userEvent.click(screen.getByRole('checkbox', {name: testProject.slug}));
expect(onChangeMock).toHaveBeenCalled();
expect(onApplyChangeMock).not.toHaveBeenCalled();
});
it('displays multi projects with non member projects', function () {
const nonMemberProject = TestStubs.Project({id: '2'});
render(, {
context: routerContext,
});
openMenu();
expect(screen.getByText("Projects I don't belong to")).toBeInTheDocument();
expect(screen.getAllByTestId('badge-display-name')).toHaveLength(3);
});
it('displays projects in alphabetical order partitioned by project membership', function () {
const projectA = TestStubs.Project({id: '1', slug: 'a-project'});
const projectB = TestStubs.Project({id: '2', slug: 'b-project'});
const projectANonM = TestStubs.Project({id: '3', slug: 'a-non-m-project'});
const projectBNonM = TestStubs.Project({id: '4', slug: 'b-non-m-project'});
const multiProjectProps = {
...props,
memberProjects: [projectB, projectA],
nonMemberProjects: [projectBNonM, projectANonM],
value: [],
};
render(, {context: routerContext});
openMenu();
expect(screen.getByText("Projects I don't belong to")).toBeInTheDocument();
const projectLabels = screen.getAllByTestId('badge-display-name');
expect(projectLabels).toHaveLength(4);
expect(projectLabels[0]).toHaveTextContent(projectA.slug);
expect(projectLabels[1]).toHaveTextContent(projectB.slug);
expect(projectLabels[2]).toHaveTextContent(projectANonM.slug);
expect(projectLabels[3]).toHaveTextContent(projectBNonM.slug);
});
it('displays multi projects in sort order rules: selected, bookmarked, alphabetical', function () {
const projectA = TestStubs.Project({id: '1', slug: 'a-project'});
const projectBBookmarked = TestStubs.Project({
id: '2',
slug: 'b-project',
isBookmarked: true,
});
const projectCBookmarked = TestStubs.Project({
id: '3',
slug: 'c-project',
isBookmarked: true,
});
const projectDSelected = TestStubs.Project({id: '4', slug: 'd-project'});
const projectESelected = TestStubs.Project({id: '5', slug: 'e-project'});
const projectFSelectedBookmarked = TestStubs.Project({
id: '6',
slug: 'f-project',
isBookmarked: true,
});
const projectGSelectedBookmarked = TestStubs.Project({
id: '7',
slug: 'g-project',
isBookmarked: true,
});
const projectH = TestStubs.Project({id: '8', slug: 'h-project'});
const projectJ = TestStubs.Project({id: '9', slug: 'j-project'});
const projectKSelectedBookmarked = TestStubs.Project({
id: '10',
slug: 'k-project',
isBookmarked: true,
});
const projectL = TestStubs.Project({id: '11', slug: 'l-project'});
const multiProjectProps = {
...props,
// XXX: Intentionally sorted arbitrarily
memberProjects: [
projectBBookmarked,
projectFSelectedBookmarked,
projectDSelected,
projectA,
projectESelected,
projectGSelectedBookmarked,
projectCBookmarked,
projectH,
],
nonMemberProjects: [projectL, projectJ, projectKSelectedBookmarked],
value: [
projectESelected.id,
projectDSelected.id,
projectGSelectedBookmarked.id,
projectFSelectedBookmarked.id,
projectKSelectedBookmarked.id,
].map(p => parseInt(p, 10)),
};
render(, {context: routerContext});
openMenu();
const projectLabels = screen.getAllByTestId('badge-display-name');
expect(projectLabels).toHaveLength(11);
// member projects
expect(projectLabels[0]).toHaveTextContent(projectFSelectedBookmarked.slug);
expect(projectLabels[1]).toHaveTextContent(projectGSelectedBookmarked.slug);
expect(projectLabels[2]).toHaveTextContent(projectDSelected.slug);
expect(projectLabels[3]).toHaveTextContent(projectESelected.slug);
expect(projectLabels[4]).toHaveTextContent(projectBBookmarked.slug);
expect(projectLabels[5]).toHaveTextContent(projectCBookmarked.slug);
expect(projectLabels[6]).toHaveTextContent(projectA.slug);
expect(projectLabels[7]).toHaveTextContent(projectH.slug);
expect(projectLabels[6]).toHaveTextContent(projectA.slug);
expect(projectLabels[7]).toHaveTextContent(projectH.slug);
// non member projects
expect(projectLabels[8]).toHaveTextContent(projectKSelectedBookmarked.slug);
expect(projectLabels[9]).toHaveTextContent(projectJ.slug);
expect(projectLabels[10]).toHaveTextContent(projectL.slug);
});
it('does not change sort order while selecting projects with the dropdown open', function () {
const projectA = TestStubs.Project({id: '1', slug: 'a-project'});
const projectBBookmarked = TestStubs.Project({
id: '2',
slug: 'b-project',
isBookmarked: true,
});
const projectDSelected = TestStubs.Project({id: '4', slug: 'd-project'});
const multiProjectProps = {
...props,
// XXX: Intentionally sorted arbitrarily
memberProjects: [projectBBookmarked, projectDSelected, projectA],
nonMemberProjects: [],
value: [projectDSelected.id].map(p => parseInt(p, 10)),
};
const {rerender} = render(, {
context: routerContext,
});
openMenu();
const projectLabels = screen.getAllByTestId('badge-display-name');
expect(projectLabels).toHaveLength(3);
// member projects
expect(projectLabels[0]).toHaveTextContent(projectDSelected.slug);
expect(projectLabels[1]).toHaveTextContent(projectBBookmarked.slug);
expect(projectLabels[2]).toHaveTextContent(projectA.slug);
// Unselect project D (re-render with the updated selection value)
userEvent.click(screen.getByRole('checkbox', {name: projectDSelected.slug}));
rerender(, {
context: routerContext,
});
// Project D is no longer checked
expect(screen.getByRole('checkbox', {name: projectDSelected.slug})).not.toBeChecked();
// Project D is still the first selected item
expect(screen.getAllByTestId('badge-display-name')[0]).toHaveTextContent(
projectDSelected.slug
);
// Open and close the menu
applyMenu();
openMenu();
const resortedProjectLabels = screen.getAllByTestId('badge-display-name');
// Project D has been moved to the bottom since it was unselected
expect(resortedProjectLabels[0]).toHaveTextContent(projectBBookmarked.slug);
expect(resortedProjectLabels[1]).toHaveTextContent(projectA.slug);
expect(resortedProjectLabels[2]).toHaveTextContent(projectDSelected.slug);
});
});