371 lines
12 KiB
JavaScript
371 lines
12 KiB
JavaScript
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
|
|
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
|
import { stubComponent } from 'helpers/stub_component';
|
|
import { TEST_HOST } from 'helpers/test_constants';
|
|
import waitForPromises from 'helpers/wait_for_promises';
|
|
import ProjectsDropdownFilter from '~/analytics/shared/components/projects_dropdown_filter.vue';
|
|
import getProjects from '~/analytics/shared/graphql/projects.query.graphql';
|
|
|
|
const projects = [
|
|
{
|
|
id: 'gid://gitlab/Project/1',
|
|
name: 'Gitlab Test',
|
|
fullPath: 'gitlab-org/gitlab-test',
|
|
avatarUrl: `${TEST_HOST}/images/home/nasa.svg`,
|
|
},
|
|
{
|
|
id: 'gid://gitlab/Project/2',
|
|
name: 'Gitlab Shell',
|
|
fullPath: 'gitlab-org/gitlab-shell',
|
|
avatarUrl: null,
|
|
},
|
|
{
|
|
id: 'gid://gitlab/Project/3',
|
|
name: 'Foo',
|
|
fullPath: 'gitlab-org/foo',
|
|
avatarUrl: null,
|
|
},
|
|
];
|
|
|
|
const MockGlDropdown = stubComponent(GlDropdown, {
|
|
template: `
|
|
<div>
|
|
<div data-testid="vsa-highlighted-items">
|
|
<slot name="highlighted-items"></slot>
|
|
</div>
|
|
<div data-testid="vsa-default-items"><slot></slot></div>
|
|
</div>
|
|
`,
|
|
});
|
|
|
|
const defaultMocks = {
|
|
$apollo: {
|
|
query: jest.fn().mockResolvedValue({
|
|
data: { group: { projects: { nodes: projects } } },
|
|
}),
|
|
},
|
|
};
|
|
|
|
let spyQuery;
|
|
|
|
describe('ProjectsDropdownFilter component', () => {
|
|
let wrapper;
|
|
|
|
const createComponent = (props = {}, stubs = {}) => {
|
|
spyQuery = defaultMocks.$apollo.query;
|
|
wrapper = mountExtended(ProjectsDropdownFilter, {
|
|
mocks: { ...defaultMocks },
|
|
propsData: {
|
|
groupId: 1,
|
|
groupNamespace: 'gitlab-org',
|
|
...props,
|
|
},
|
|
stubs,
|
|
});
|
|
};
|
|
|
|
const createWithMockDropdown = (props) => {
|
|
createComponent(props, { GlDropdown: MockGlDropdown });
|
|
return waitForPromises();
|
|
};
|
|
|
|
afterEach(() => {
|
|
wrapper.destroy();
|
|
});
|
|
|
|
const findHighlightedItems = () => wrapper.findByTestId('vsa-highlighted-items');
|
|
const findUnhighlightedItems = () => wrapper.findByTestId('vsa-default-items');
|
|
const findHighlightedItemsTitle = () => wrapper.findByText('Selected');
|
|
const findClearAllButton = () => wrapper.findByText('Clear all');
|
|
|
|
const findDropdown = () => wrapper.find(GlDropdown);
|
|
|
|
const findDropdownItems = () =>
|
|
findDropdown()
|
|
.findAll(GlDropdownItem)
|
|
.filter((w) => w.text() !== 'No matching results');
|
|
|
|
const findDropdownAtIndex = (index) => findDropdownItems().at(index);
|
|
|
|
const findDropdownButton = () => findDropdown().find('.dropdown-toggle');
|
|
const findDropdownButtonAvatar = () => findDropdown().find('.gl-avatar');
|
|
const findDropdownButtonAvatarAtIndex = (index) =>
|
|
findDropdownAtIndex(index).find('img.gl-avatar');
|
|
const findDropdownButtonIdentIconAtIndex = (index) =>
|
|
findDropdownAtIndex(index).find('div.gl-avatar-identicon');
|
|
|
|
const findDropdownNameAtIndex = (index) =>
|
|
findDropdownAtIndex(index).find('[data-testid="project-name"');
|
|
const findDropdownFullPathAtIndex = (index) =>
|
|
findDropdownAtIndex(index).find('[data-testid="project-full-path"]');
|
|
|
|
const selectDropdownItemAtIndex = (index) => {
|
|
findDropdownAtIndex(index).find('button').trigger('click');
|
|
return wrapper.vm.$nextTick();
|
|
};
|
|
|
|
// NOTE: Selected items are now visually separated from unselected items
|
|
const findSelectedDropdownItems = () => findHighlightedItems().findAll(GlDropdownItem);
|
|
|
|
const findSelectedDropdownAtIndex = (index) => findSelectedDropdownItems().at(index);
|
|
const findSelectedButtonIdentIconAtIndex = (index) =>
|
|
findSelectedDropdownAtIndex(index).find('div.gl-avatar-identicon');
|
|
const findSelectedButtonAvatarItemAtIndex = (index) =>
|
|
findSelectedDropdownAtIndex(index).find('img.gl-avatar');
|
|
|
|
const selectedIds = () => wrapper.vm.selectedProjects.map(({ id }) => id);
|
|
|
|
describe('queryParams are applied when fetching data', () => {
|
|
beforeEach(() => {
|
|
createComponent({
|
|
queryParams: {
|
|
first: 50,
|
|
includeSubgroups: true,
|
|
},
|
|
});
|
|
});
|
|
|
|
it('applies the correct queryParams when making an api call', async () => {
|
|
wrapper.setData({ searchTerm: 'gitlab' });
|
|
|
|
expect(spyQuery).toHaveBeenCalledTimes(1);
|
|
|
|
await wrapper.vm.$nextTick(() => {
|
|
expect(spyQuery).toHaveBeenCalledWith({
|
|
query: getProjects,
|
|
variables: {
|
|
search: 'gitlab',
|
|
groupFullPath: wrapper.vm.groupNamespace,
|
|
first: 50,
|
|
includeSubgroups: true,
|
|
},
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('highlighted items', () => {
|
|
const blockDefaultProps = { multiSelect: true };
|
|
beforeEach(() => {
|
|
createComponent(blockDefaultProps);
|
|
});
|
|
|
|
describe('with no project selected', () => {
|
|
it('does not render the highlighted items', async () => {
|
|
await createWithMockDropdown(blockDefaultProps);
|
|
expect(findSelectedDropdownItems().length).toBe(0);
|
|
});
|
|
|
|
it('does not render the highlighted items title', () => {
|
|
expect(findHighlightedItemsTitle().exists()).toBe(false);
|
|
});
|
|
|
|
it('does not render the clear all button', () => {
|
|
expect(findClearAllButton().exists()).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('with a selected project', () => {
|
|
beforeEach(async () => {
|
|
await selectDropdownItemAtIndex(0);
|
|
});
|
|
|
|
it('renders the highlighted items', async () => {
|
|
await createWithMockDropdown(blockDefaultProps);
|
|
await selectDropdownItemAtIndex(0);
|
|
|
|
expect(findSelectedDropdownItems().length).toBe(1);
|
|
});
|
|
|
|
it('renders the highlighted items title', () => {
|
|
expect(findHighlightedItemsTitle().exists()).toBe(true);
|
|
});
|
|
|
|
it('renders the clear all button', () => {
|
|
expect(findClearAllButton().exists()).toBe(true);
|
|
});
|
|
|
|
it('clears all selected items when the clear all button is clicked', async () => {
|
|
await selectDropdownItemAtIndex(1);
|
|
|
|
expect(wrapper.text()).toContain('2 projects selected');
|
|
|
|
findClearAllButton().trigger('click');
|
|
await wrapper.vm.$nextTick();
|
|
|
|
expect(wrapper.text()).not.toContain('2 projects selected');
|
|
expect(wrapper.text()).toContain('Select projects');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('with a selected project and search term', () => {
|
|
beforeEach(async () => {
|
|
await createWithMockDropdown({ multiSelect: true });
|
|
|
|
selectDropdownItemAtIndex(0);
|
|
wrapper.setData({ searchTerm: 'this is a very long search string' });
|
|
});
|
|
|
|
it('renders the highlighted items', async () => {
|
|
expect(findUnhighlightedItems().findAll('li').length).toBe(1);
|
|
});
|
|
|
|
it('hides the unhighlighted items that do not match the string', async () => {
|
|
expect(findUnhighlightedItems().findAll('li').length).toBe(1);
|
|
expect(findUnhighlightedItems().text()).toContain('No matching results');
|
|
});
|
|
});
|
|
|
|
describe('when passed an array of defaultProject as prop', () => {
|
|
beforeEach(() => {
|
|
createComponent({
|
|
defaultProjects: [projects[0]],
|
|
});
|
|
});
|
|
|
|
it("displays the defaultProject's name", () => {
|
|
expect(findDropdownButton().text()).toContain(projects[0].name);
|
|
});
|
|
|
|
it("renders the defaultProject's avatar", () => {
|
|
expect(findDropdownButtonAvatar().exists()).toBe(true);
|
|
});
|
|
|
|
it('marks the defaultProject as selected', () => {
|
|
expect(findDropdownAtIndex(0).props('isChecked')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('when multiSelect is false', () => {
|
|
const blockDefaultProps = { multiSelect: false };
|
|
beforeEach(() => {
|
|
createComponent(blockDefaultProps);
|
|
});
|
|
|
|
describe('displays the correct information', () => {
|
|
it('contains 3 items', () => {
|
|
expect(findDropdownItems()).toHaveLength(3);
|
|
});
|
|
|
|
it('renders an avatar when the project has an avatarUrl', () => {
|
|
expect(findDropdownButtonAvatarAtIndex(0).exists()).toBe(true);
|
|
expect(findDropdownButtonIdentIconAtIndex(0).exists()).toBe(false);
|
|
});
|
|
|
|
it("renders an identicon when the project doesn't have an avatarUrl", () => {
|
|
expect(findDropdownButtonAvatarAtIndex(1).exists()).toBe(false);
|
|
expect(findDropdownButtonIdentIconAtIndex(1).exists()).toBe(true);
|
|
});
|
|
|
|
it('renders the project name', () => {
|
|
projects.forEach((project, index) => {
|
|
expect(findDropdownNameAtIndex(index).text()).toBe(project.name);
|
|
});
|
|
});
|
|
|
|
it('renders the project fullPath', () => {
|
|
projects.forEach((project, index) => {
|
|
expect(findDropdownFullPathAtIndex(index).text()).toBe(project.fullPath);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('on project click', () => {
|
|
it('should emit the "selected" event with the selected project', () => {
|
|
selectDropdownItemAtIndex(0);
|
|
|
|
expect(wrapper.emitted().selected).toEqual([[[projects[0]]]]);
|
|
});
|
|
|
|
it('should change selection when new project is clicked', () => {
|
|
selectDropdownItemAtIndex(1);
|
|
|
|
expect(wrapper.emitted().selected).toEqual([[[projects[1]]]]);
|
|
});
|
|
|
|
it('selection should be emptied when a project is deselected', () => {
|
|
selectDropdownItemAtIndex(0); // Select the item
|
|
selectDropdownItemAtIndex(0); // deselect it
|
|
|
|
expect(wrapper.emitted().selected).toEqual([[[projects[0]]], [[]]]);
|
|
});
|
|
|
|
it('renders an avatar in the dropdown button when the project has an avatarUrl', async () => {
|
|
await createWithMockDropdown(blockDefaultProps);
|
|
await selectDropdownItemAtIndex(0);
|
|
|
|
expect(findSelectedButtonAvatarItemAtIndex(0).exists()).toBe(true);
|
|
expect(findSelectedButtonIdentIconAtIndex(0).exists()).toBe(false);
|
|
});
|
|
|
|
it("renders an identicon in the dropdown button when the project doesn't have an avatarUrl", async () => {
|
|
await createWithMockDropdown(blockDefaultProps);
|
|
await selectDropdownItemAtIndex(1);
|
|
|
|
expect(findSelectedButtonAvatarItemAtIndex(0).exists()).toBe(false);
|
|
expect(findSelectedButtonIdentIconAtIndex(0).exists()).toBe(true);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('when multiSelect is true', () => {
|
|
beforeEach(() => {
|
|
createComponent({ multiSelect: true });
|
|
});
|
|
|
|
describe('displays the correct information', () => {
|
|
it('contains 3 items', () => {
|
|
expect(findDropdownItems()).toHaveLength(3);
|
|
});
|
|
|
|
it('renders an avatar when the project has an avatarUrl', () => {
|
|
expect(findDropdownButtonAvatarAtIndex(0).exists()).toBe(true);
|
|
expect(findDropdownButtonIdentIconAtIndex(0).exists()).toBe(false);
|
|
});
|
|
|
|
it("renders an identicon when the project doesn't have an avatarUrl", () => {
|
|
expect(findDropdownButtonAvatarAtIndex(1).exists()).toBe(false);
|
|
expect(findDropdownButtonIdentIconAtIndex(1).exists()).toBe(true);
|
|
});
|
|
|
|
it('renders the project name', () => {
|
|
projects.forEach((project, index) => {
|
|
expect(findDropdownNameAtIndex(index).text()).toBe(project.name);
|
|
});
|
|
});
|
|
|
|
it('renders the project fullPath', () => {
|
|
projects.forEach((project, index) => {
|
|
expect(findDropdownFullPathAtIndex(index).text()).toBe(project.fullPath);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('on project click', () => {
|
|
it('should add to selection when new project is clicked', () => {
|
|
selectDropdownItemAtIndex(0);
|
|
selectDropdownItemAtIndex(1);
|
|
|
|
expect(selectedIds()).toEqual([projects[0].id, projects[1].id]);
|
|
});
|
|
|
|
it('should remove from selection when clicked again', () => {
|
|
selectDropdownItemAtIndex(0);
|
|
expect(selectedIds()).toEqual([projects[0].id]);
|
|
|
|
selectDropdownItemAtIndex(0);
|
|
expect(selectedIds()).toEqual([]);
|
|
});
|
|
|
|
it('renders the correct placeholder text when multiple projects are selected', async () => {
|
|
selectDropdownItemAtIndex(0);
|
|
selectDropdownItemAtIndex(1);
|
|
|
|
await wrapper.vm.$nextTick().then(() => {
|
|
expect(findDropdownButton().text()).toBe('2 projects selected');
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|