|
@@ -1,452 +0,0 @@
|
|
|
-import {Organization} from 'sentry-fixture/organization';
|
|
|
-
|
|
|
-import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
|
|
|
-
|
|
|
-import ProjectSelector, {
|
|
|
- ProjectSelectorProps,
|
|
|
-} from 'sentry/components/organizations/projectSelector';
|
|
|
-import {ALL_ACCESS_PROJECTS} from 'sentry/constants/pageFilters';
|
|
|
-
|
|
|
-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 = Organization({
|
|
|
- id: '1',
|
|
|
- slug: 'org',
|
|
|
- features: ['new-teams', 'global-views'],
|
|
|
- access: [],
|
|
|
- });
|
|
|
-
|
|
|
- const routerContext = TestStubs.routerContext([{organization: mockOrg}]);
|
|
|
-
|
|
|
- async function openMenu() {
|
|
|
- await userEvent.click(screen.getByRole('button'));
|
|
|
- }
|
|
|
-
|
|
|
- async function applyMenu() {
|
|
|
- await userEvent.click(screen.getByRole('button', {name: 'Apply Filter'}));
|
|
|
- }
|
|
|
-
|
|
|
- const props: ProjectSelectorProps = {
|
|
|
- customDropdownButton: () => <span>Project Picker</span>,
|
|
|
- customLoadingIndicator: () => 'Loading...',
|
|
|
- isGlobalSelectionReady: true,
|
|
|
- organization: mockOrg,
|
|
|
- memberProjects: [testProject, anotherProject],
|
|
|
- nonMemberProjects: [],
|
|
|
- value: [],
|
|
|
- onApplyChange: () => {},
|
|
|
- onChange: () => {},
|
|
|
- };
|
|
|
-
|
|
|
- it('should show empty message with no projects button, when no projects, and has no "project:write" access', async function () {
|
|
|
- render(
|
|
|
- <ProjectSelector
|
|
|
- {...props}
|
|
|
- memberProjects={[]}
|
|
|
- organization={Organization({
|
|
|
- id: 'org',
|
|
|
- slug: 'org-slug',
|
|
|
- access: [],
|
|
|
- features: [],
|
|
|
- })}
|
|
|
- />,
|
|
|
- {context: routerContext}
|
|
|
- );
|
|
|
-
|
|
|
- await 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', async function () {
|
|
|
- render(
|
|
|
- <ProjectSelector
|
|
|
- {...props}
|
|
|
- memberProjects={[]}
|
|
|
- organization={Organization({
|
|
|
- id: 'org',
|
|
|
- slug: 'org-slug',
|
|
|
- access: ['project:write'],
|
|
|
- features: [],
|
|
|
- })}
|
|
|
- />,
|
|
|
- {context: routerContext}
|
|
|
- );
|
|
|
-
|
|
|
- await 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', async function () {
|
|
|
- render(<ProjectSelector {...props} disabled />, {context: routerContext});
|
|
|
- await openMenu();
|
|
|
-
|
|
|
- expect(screen.queryByRole('checkbox')).not.toBeInTheDocument();
|
|
|
- });
|
|
|
-
|
|
|
- it('lists projects and has filter', async function () {
|
|
|
- render(<ProjectSelector {...props} />, {context: routerContext});
|
|
|
- await openMenu();
|
|
|
-
|
|
|
- expect(screen.getByText(testProject.slug)).toBeInTheDocument();
|
|
|
- expect(screen.getByText(anotherProject.slug)).toBeInTheDocument();
|
|
|
- });
|
|
|
-
|
|
|
- it('can filter projects by project name', async function () {
|
|
|
- render(<ProjectSelector {...props} />, {context: routerContext});
|
|
|
- await openMenu();
|
|
|
-
|
|
|
- screen.getByRole('textbox').focus();
|
|
|
- await 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', async function () {
|
|
|
- render(<ProjectSelector {...props} />, {context: routerContext});
|
|
|
- await openMenu();
|
|
|
-
|
|
|
- screen.getByRole('textbox').focus();
|
|
|
- await 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', async function () {
|
|
|
- render(<ProjectSelector {...props} />, {context: routerContext});
|
|
|
- await openMenu();
|
|
|
-
|
|
|
- await userEvent.click(screen.getByRole('textbox'));
|
|
|
-
|
|
|
- // Dropdown is still open
|
|
|
- expect(screen.getByText(testProject.slug)).toBeInTheDocument();
|
|
|
- });
|
|
|
-
|
|
|
- it('closes dropdown when project is selected', async function () {
|
|
|
- render(<ProjectSelector {...props} />, {context: routerContext});
|
|
|
- await openMenu();
|
|
|
-
|
|
|
- await userEvent.click(screen.getByText(testProject.slug));
|
|
|
-
|
|
|
- // Dropdown is closed
|
|
|
- expect(screen.queryByText(testProject.slug)).not.toBeInTheDocument();
|
|
|
- });
|
|
|
-
|
|
|
- it('calls callback when project is selected', async function () {
|
|
|
- const onApplyChangeMock = jest.fn();
|
|
|
- render(<ProjectSelector {...props} onApplyChange={onApplyChangeMock} />, {
|
|
|
- context: routerContext,
|
|
|
- });
|
|
|
- await openMenu();
|
|
|
-
|
|
|
- // Select first project
|
|
|
- await userEvent.click(screen.getByText(testProject.slug));
|
|
|
-
|
|
|
- expect(onApplyChangeMock).toHaveBeenCalledWith([parseInt(testProject.id, 10)]);
|
|
|
- });
|
|
|
-
|
|
|
- it('does not call `onUpdate` when using multi select', async function () {
|
|
|
- const onChangeMock = jest.fn();
|
|
|
- const onApplyChangeMock = jest.fn();
|
|
|
- render(
|
|
|
- <ProjectSelector
|
|
|
- {...props}
|
|
|
- onChange={onChangeMock}
|
|
|
- onApplyChange={onApplyChangeMock}
|
|
|
- />,
|
|
|
- {context: routerContext}
|
|
|
- );
|
|
|
- await openMenu();
|
|
|
-
|
|
|
- // Check the first project
|
|
|
- await userEvent.click(screen.getByRole('checkbox', {name: testProject.slug}));
|
|
|
-
|
|
|
- expect(onChangeMock).toHaveBeenCalled();
|
|
|
- expect(onApplyChangeMock).not.toHaveBeenCalled();
|
|
|
- });
|
|
|
-
|
|
|
- it('displays multi projects with non member projects', async function () {
|
|
|
- const nonMemberProject = TestStubs.Project({id: '2'});
|
|
|
-
|
|
|
- render(<ProjectSelector {...props} nonMemberProjects={[nonMemberProject]} />, {
|
|
|
- context: routerContext,
|
|
|
- });
|
|
|
- await 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', async 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(<ProjectSelector {...multiProjectProps} />, {context: routerContext});
|
|
|
- await 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', async 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(<ProjectSelector {...multiProjectProps} />, {context: routerContext});
|
|
|
- await 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', async 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(<ProjectSelector {...multiProjectProps} />, {
|
|
|
- context: routerContext,
|
|
|
- });
|
|
|
-
|
|
|
- await 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)
|
|
|
- await userEvent.click(screen.getByRole('checkbox', {name: projectDSelected.slug}));
|
|
|
- rerender(<ProjectSelector {...multiProjectProps} value={[]} />);
|
|
|
-
|
|
|
- // 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
|
|
|
- await applyMenu();
|
|
|
- await 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);
|
|
|
- });
|
|
|
-
|
|
|
- it('can select all projects when role=owner', async function () {
|
|
|
- const mockOnApplyChange = jest.fn();
|
|
|
- render(
|
|
|
- <ProjectSelector
|
|
|
- {...props}
|
|
|
- nonMemberProjects={[anotherProject]}
|
|
|
- organization={Organization({...mockOrg, orgRole: 'owner'})}
|
|
|
- onApplyChange={mockOnApplyChange}
|
|
|
- />,
|
|
|
- {context: routerContext}
|
|
|
- );
|
|
|
-
|
|
|
- await openMenu();
|
|
|
-
|
|
|
- await userEvent.click(screen.getByRole('button', {name: 'Select All Projects'}));
|
|
|
-
|
|
|
- expect(mockOnApplyChange).toHaveBeenCalledTimes(1);
|
|
|
- expect(mockOnApplyChange).toHaveBeenCalledWith([ALL_ACCESS_PROJECTS]);
|
|
|
- });
|
|
|
-
|
|
|
- it('can select all projects when role=manager', async function () {
|
|
|
- const mockOnApplyChange = jest.fn();
|
|
|
- render(
|
|
|
- <ProjectSelector
|
|
|
- {...props}
|
|
|
- nonMemberProjects={[anotherProject]}
|
|
|
- organization={Organization({...mockOrg, orgRole: 'manager'})}
|
|
|
- onApplyChange={mockOnApplyChange}
|
|
|
- />,
|
|
|
- {context: routerContext}
|
|
|
- );
|
|
|
-
|
|
|
- await openMenu();
|
|
|
-
|
|
|
- await userEvent.click(screen.getByRole('button', {name: 'Select All Projects'}));
|
|
|
-
|
|
|
- expect(mockOnApplyChange).toHaveBeenCalledTimes(1);
|
|
|
- expect(mockOnApplyChange).toHaveBeenCalledWith([ALL_ACCESS_PROJECTS]);
|
|
|
- });
|
|
|
-
|
|
|
- it('can select all projects when org has open membership', async function () {
|
|
|
- const mockOnApplyChange = jest.fn();
|
|
|
- render(
|
|
|
- <ProjectSelector
|
|
|
- {...props}
|
|
|
- nonMemberProjects={[anotherProject]}
|
|
|
- organization={Organization({
|
|
|
- ...mockOrg,
|
|
|
- features: [...mockOrg.features, 'open-membership'],
|
|
|
- })}
|
|
|
- onApplyChange={mockOnApplyChange}
|
|
|
- />,
|
|
|
- {context: routerContext}
|
|
|
- );
|
|
|
-
|
|
|
- await openMenu();
|
|
|
-
|
|
|
- await userEvent.click(screen.getByRole('button', {name: 'Select All Projects'}));
|
|
|
-
|
|
|
- expect(mockOnApplyChange).toHaveBeenCalledTimes(1);
|
|
|
- expect(mockOnApplyChange).toHaveBeenCalledWith([ALL_ACCESS_PROJECTS]);
|
|
|
- });
|
|
|
-
|
|
|
- it('can select all projects after first selecting a project', async function () {
|
|
|
- const mockOnApplyChange = jest.fn();
|
|
|
- render(
|
|
|
- <ProjectSelector
|
|
|
- {...props}
|
|
|
- nonMemberProjects={[anotherProject]}
|
|
|
- organization={Organization({...mockOrg, orgRole: 'manager'})}
|
|
|
- onApplyChange={mockOnApplyChange}
|
|
|
- />,
|
|
|
- {context: routerContext}
|
|
|
- );
|
|
|
-
|
|
|
- await openMenu();
|
|
|
-
|
|
|
- // Check the first project
|
|
|
- await userEvent.click(screen.getByRole('checkbox', {name: testProject.slug}));
|
|
|
-
|
|
|
- await userEvent.click(screen.getByRole('button', {name: 'Select All Projects'}));
|
|
|
-
|
|
|
- // Menu should close and all projects should be selected, not previously selected project
|
|
|
- expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
|
|
|
- expect(mockOnApplyChange).toHaveBeenCalledTimes(1);
|
|
|
- expect(mockOnApplyChange).toHaveBeenCalledWith([ALL_ACCESS_PROJECTS]);
|
|
|
- });
|
|
|
-});
|