Commit 86665d7d authored by Martin Wortschack's avatar Martin Wortschack

Switch to graphql in projects dropdown

- This MR switches to the grapqhl API in the project dropdown selector.
parent a58d9a88
......@@ -210,6 +210,7 @@ export default {
:key="currentGroup.id"
class="js-projects-dropdown-filter project-select"
:group-id="currentGroup.id"
:group-namespace="currentGroupPath"
:query-params="projectsQueryParams"
:multi-select="$options.multiProjectSelect"
:default-projects="selectedProjects"
......
......@@ -4,7 +4,6 @@ import GroupsDropdownFilter from '../../shared/components/groups_dropdown_filter
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ProjectsDropdownFilter from '../../shared/components/projects_dropdown_filter.vue';
import { accessLevelReporter, projectsPerPage } from '../constants';
import { SIMILARITY_ORDER, LAST_ACTIVITY_AT } from '../../shared/constants';
export default {
components: {
......@@ -44,10 +43,8 @@ export default {
},
projectsQueryParams() {
return {
per_page: projectsPerPage,
with_shared: false, // exclude forks
order_by: this.glFeatures.analyticsSimilaritySearch ? SIMILARITY_ORDER : LAST_ACTIVITY_AT,
include_subgroups: true,
first: projectsPerPage,
includeSubgroups: true,
};
},
},
......@@ -59,13 +56,8 @@ export default {
this.$emit('groupSelected', { groupId: id, groupNamespace: full_path });
},
onProjectsSelected(selectedProjects) {
let projectNamespace = null;
let projectId = null;
if (selectedProjects.length) {
projectNamespace = selectedProjects[0].path_with_namespace;
projectId = selectedProjects[0].id;
}
const projectNamespace = selectedProjects[0]?.fullPath || null;
const projectId = selectedProjects[0]?.id || null;
this.setProjectPath(projectNamespace);
this.$emit('projectSelected', {
......@@ -98,6 +90,8 @@ export default {
:default-projects="projects"
:query-params="projectsQueryParams"
:group-id="groupId"
:group-namespace="groupNamespace"
:use-graphql="true"
@selected="onProjectsSelected"
/>
</div>
......
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { mapState, mapActions } from 'vuex';
import store from './store';
import FilterDropdowns from './components/filter_dropdowns.vue';
import DateRange from '../shared/components/daterange.vue';
import ProductivityAnalyticsApp from './components/app.vue';
import FilteredSearchProductivityAnalytics from './filtered_search_productivity_analytics';
import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { getLabelsEndpoint, getMilestonesEndpoint } from './utils';
import { buildGroupFromDataset, buildProjectFromDataset } from '../shared/utils';
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
export default () => {
const container = document.getElementById('js-productivity-analytics');
const groupProjectSelectContainer = container.querySelector('.js-group-project-select-container');
......@@ -65,6 +74,7 @@ export default () => {
// eslint-disable-next-line no-new
new Vue({
el: groupProjectSelectContainer,
apolloProvider,
store,
created() {
// let's not fetch any data by default since we might not have a valid group yet
......@@ -76,8 +86,8 @@ export default () => {
this.initFilteredSearch({
groupNamespace: group.full_path,
groupId: group.id,
projectNamespace: project ? project.path_with_namespace : null,
projectId: project ? project.id : null,
projectNamespace: project?.path_with_namespace || null,
projectId: container.dataset.projectId || null,
});
// let's fetch data now since we do have a valid group
......@@ -93,7 +103,12 @@ export default () => {
this.initFilteredSearch({ groupNamespace, groupId });
},
onProjectSelected({ groupNamespace, groupId, projectNamespace, projectId }) {
this.initFilteredSearch({ groupNamespace, groupId, projectNamespace, projectId });
this.initFilteredSearch({
groupNamespace,
groupId,
projectNamespace,
projectId: getIdFromGraphQLId(projectId),
});
},
initFilteredSearch({ groupNamespace, groupId, projectNamespace = '', projectId = null }) {
// let's unbind attached event handlers first and reset the template
......
......@@ -11,8 +11,10 @@ import {
} from '@gitlab/ui';
import { n__, s__, __ } from '~/locale';
import Api from '~/api';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { DATA_REFETCH_DELAY } from '../constants';
import { filterBySearchTerm } from '../utils';
import getProjects from '../graphql/projects.query.graphql';
export default {
name: 'ProjectsDropdownFilter',
......@@ -30,6 +32,10 @@ export default {
type: Number,
required: true,
},
groupNamespace: {
type: String,
required: true,
},
multiSelect: {
type: Boolean,
required: false,
......@@ -50,6 +56,11 @@ export default {
required: false,
default: () => [],
},
useGraphql: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
......@@ -121,6 +132,31 @@ export default {
},
fetchData() {
this.loading = true;
if (this.useGraphql) {
return this.$apollo
.query({
query: getProjects,
variables: {
groupFullPath: this.groupNamespace,
search: this.searchTerm,
...this.queryParams,
},
})
.then(response => {
const {
data: {
group: {
projects: { nodes },
},
},
} = response;
this.loading = false;
this.projects = nodes;
});
}
return Api.groupProjects(this.groupId, this.searchTerm, this.queryParams, projects => {
this.projects = projects;
this.loading = false;
......@@ -129,6 +165,11 @@ export default {
isProjectSelected(id) {
return this.selectedProjects ? this.selectedProjectIds.includes(id) : false;
},
getEntityId(project) {
if (this.useGraphql) return getIdFromGraphQLId(project.id);
return project?.id || null;
},
},
};
</script>
......@@ -143,8 +184,8 @@ export default {
<div class="gl-display-flex gl-flex-fill-1">
<gl-avatar
v-if="isOnlyOneProjectSelected"
:src="selectedProjects[0].avatar_url"
:entity-id="selectedProjects[0].id"
:src="useGraphql ? selectedProjects[0].avatarUrl : selectedProjects[0].avatar_url"
:entity-id="getEntityId(selectedProjects[0])"
:entity-name="selectedProjects[0].name"
:size="16"
shape="rect"
......@@ -170,9 +211,9 @@ export default {
class="gl-mr-2 vertical-align-middle"
:alt="project.name"
:size="16"
:entity-id="project.id"
:entity-id="getEntityId(project)"
:entity-name="project.name"
:src="project.avatar_url"
:src="useGraphql ? project.avatarUrl : project.avatar_url"
shape="rect"
/>
{{ project.name }}
......
query getGroupProjects(
$groupFullPath: ID!
$search: String!
$first: Int!
$includeSubgroups: Boolean = false
) {
group(fullPath: $groupFullPath) {
projects(
search: $search
first: $first
includeSubgroups: $includeSubgroups
sort: SIMILARITY
) {
nodes {
id
name
avatarUrl
fullPath
}
}
}
}
......@@ -39,11 +39,11 @@ export const buildGroupFromDataset = dataset => {
* @returns {Object} - A project object
*/
export const buildProjectFromDataset = dataset => {
const { projectId, projectName, projectPathWithNamespace, projectAvatarUrl } = dataset;
const { projectGid, projectName, projectPathWithNamespace, projectAvatarUrl } = dataset;
if (projectId) {
if (projectGid) {
return {
id: Number(projectId),
id: projectGid,
name: projectName,
path_with_namespace: projectPathWithNamespace,
avatar_url: projectAvatarUrl,
......
......@@ -66,6 +66,7 @@ module Analytics
def project_data_attributes
{
id: project.id,
gid: project.to_gid.to_s,
name: project.name,
path_with_namespace: project.path_with_namespace,
avatar_url: project.avatar_url
......
......@@ -21,7 +21,7 @@ describe('FilterDropdowns component', () => {
const groupId = 1;
const groupNamespace = 'gitlab-org';
const projectPath = 'gitlab-org/gitlab-test';
const projectId = 10;
const projectId = 'gid://gitlab/Project/1';
beforeEach(() => {
const {
......@@ -41,6 +41,7 @@ describe('FilterDropdowns component', () => {
...modules,
},
});
wrapper = shallowMount(FilterDropdowns, {
localVue,
store: mockStore,
......@@ -71,6 +72,7 @@ describe('FilterDropdowns component', () => {
describe('with a group selected', () => {
beforeEach(() => {
wrapper.vm.groupId = groupId;
mockStore.state.filters.groupNamespace = groupNamespace;
});
it('renders the projects dropdown', () => {
......@@ -107,7 +109,7 @@ describe('FilterDropdowns component', () => {
describe('when the list of selected projects is not empty', () => {
beforeEach(() => {
mockStore.state.filters.groupNamespace = groupNamespace;
wrapper.vm.onProjectsSelected([{ id: projectId, path_with_namespace: `${projectPath}` }]);
wrapper.vm.onProjectsSelected([{ id: projectId, fullPath: `${projectPath}` }]);
});
it('invokes setProjectPath action', () => {
......
import { mount } from '@vue/test-utils';
import ProjectsDropdownFilter from 'ee/analytics/shared/components/projects_dropdown_filter.vue';
import getProjects from 'ee/analytics/shared/graphql/projects.query.graphql';
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { LAST_ACTIVITY_AT } from 'ee/analytics/shared/constants';
import { TEST_HOST } from 'helpers/test_constants';
......@@ -9,6 +10,27 @@ jest.mock('~/api', () => ({
groupProjects: jest.fn(),
}));
const mockGraphqlProjects = [
{
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 projects = [
{
id: 1,
......@@ -27,13 +49,26 @@ const projects = [
},
];
const defaultMocks = {
$apollo: {
query: jest.fn().mockResolvedValue({
data: { group: { projects: { nodes: mockGraphqlProjects } } },
}),
},
};
let spyQuery;
describe('ProjectsDropdownFilter component', () => {
let wrapper;
const createComponent = (props = {}) => {
spyQuery = defaultMocks.$apollo.query;
wrapper = mount(ProjectsDropdownFilter, {
mocks: { ...defaultMocks },
propsData: {
groupId: 1,
groupNamespace: 'gitlab-org',
...props,
},
});
......@@ -43,12 +78,6 @@ describe('ProjectsDropdownFilter component', () => {
wrapper.destroy();
});
beforeEach(() => {
Api.groupProjects.mockImplementation((groupId, term, options, callback) => {
callback(projects);
});
});
const findDropdown = () => wrapper.find(GlDropdown);
const findDropdownItems = () =>
......@@ -60,203 +89,362 @@ describe('ProjectsDropdownFilter component', () => {
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 selectDropdownItemAtIndex = index =>
findDropdownAtIndex(index)
.find('button')
.trigger('click');
describe('queryParams are applied when fetching data', () => {
describe('when using the REST API', () => {
describe('queryParams are applied when fetching data', () => {
beforeEach(() => {
Api.groupProjects.mockImplementation((groupId, term, options, callback) => {
callback(projects);
});
createComponent({
queryParams: {
per_page: 50,
with_shared: false,
order_by: LAST_ACTIVITY_AT,
},
});
});
it('applies the correct queryParams when making an api call', () => {
expect(Api.groupProjects).toHaveBeenCalledWith(
expect.any(Number),
expect.any(String),
expect.objectContaining({ per_page: 50, with_shared: false, order_by: LAST_ACTIVITY_AT }),
expect.any(Function),
);
});
});
});
describe('when using the GraphQL API', () => {
beforeEach(() => {
createComponent({
useGraphql: true,
queryParams: {
per_page: 50,
with_shared: false,
order_by: LAST_ACTIVITY_AT,
first: 50,
includeSubgroups: true,
},
});
});
it('applies the correct queryParams when making an api call', () => {
expect(Api.groupProjects).toHaveBeenCalledWith(
expect.any(Number),
expect.any(String),
expect.objectContaining({ per_page: 50, with_shared: false, order_by: LAST_ACTIVITY_AT }),
expect.any(Function),
);
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('when passed a an array of defaultProject as prop', () => {
beforeEach(() => {
createComponent({
defaultProjects: [projects[0]],
describe('when using the RESTP API', () => {
beforeEach(() => {
Api.groupProjects.mockImplementation((groupId, term, options, callback) => {
callback(projects);
});
createComponent({
defaultProjects: [projects[0]],
});
});
});
it("displays the defaultProject's name", () => {
expect(findDropdownButton().text()).toContain(projects[0].name);
});
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("renders the defaultProject's avatar", () => {
expect(findDropdownButtonAvatar().exists()).toBe(true);
});
it('marks the defaultProject as selected', () => {
expect(findDropdownAtIndex(0).props('isChecked')).toBe(true);
it('marks the defaultProject as selected', () => {
expect(findDropdownAtIndex(0).props('isChecked')).toBe(true);
});
});
});
describe('when multiSelect is false', () => {
beforeEach(() => {
createComponent({ multiSelect: false });
});
describe('when using the GraphQL API', () => {
beforeEach(() => {
createComponent({
useGraphql: true,
defaultProjects: [mockGraphqlProjects[0]],
});
});
describe('displays the correct information', () => {
it('contains 3 items', () => {
expect(findDropdownItems()).toHaveLength(3);
it("displays the defaultProject's name", () => {
expect(findDropdownButton().text()).toContain(mockGraphqlProjects[0].name);
});
it('renders an avatar when the project has an avatar_url', () => {
expect(
findDropdownAtIndex(0)
.find('img.gl-avatar')
.exists(),
).toBe(true);
expect(
findDropdownAtIndex(0)
.find('div.gl-avatar-identicon')
.exists(),
).toBe(false);
it("renders the defaultProject's avatar", () => {
expect(findDropdownButtonAvatar().exists()).toBe(true);
});
it("renders an identicon when the project doesn't have an avatar_url", () => {
expect(
findDropdownAtIndex(1)
.find('img.gl-avatar')
.exists(),
).toBe(false);
expect(
findDropdownAtIndex(1)
.find('div.gl-avatar-identicon')
.exists(),
).toBe(true);
it('marks the defaultProject as selected', () => {
expect(findDropdownAtIndex(0).props('isChecked')).toBe(true);
});
});
});
describe('on project click', () => {
it('should emit the "selected" event with the selected project', () => {
selectDropdownItemAtIndex(0);
describe('when multiSelect is false', () => {
describe('when using the RESTP API', () => {
beforeEach(() => {
Api.groupProjects.mockImplementation((groupId, term, options, callback) => {
callback(projects);
});
expect(wrapper.emitted().selected).toEqual([[[projects[0]]]]);
createComponent({ multiSelect: false });
});
it('should change selection when new project is clicked', () => {
selectDropdownItemAtIndex(1);
describe('displays the correct information', () => {
it('contains 3 items', () => {
expect(findDropdownItems()).toHaveLength(3);
});
expect(wrapper.emitted().selected).toEqual([[[projects[1]]]]);
it('renders an avatar when the project has an avatar_url', () => {
expect(findDropdownButtonAvatarAtIndex(0).exists()).toBe(true);
expect(findDropdownButtonIdentIconAtIndex(0).exists()).toBe(false);
});
it("renders an identicon when the project doesn't have an avatar_url", () => {
expect(findDropdownButtonAvatarAtIndex(1).exists()).toBe(false);
expect(findDropdownButtonIdentIconAtIndex(1).exists()).toBe(true);
});
});
it('selection should be emptied when a project is deselected', () => {
selectDropdownItemAtIndex(0); // Select the item
selectDropdownItemAtIndex(0); // deselect it
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 avatar_url', async () => {
selectDropdownItemAtIndex(0);
await wrapper.vm.$nextTick().then(() => {
expect(
findDropdownButton()
.find('img.gl-avatar')
.exists(),
).toBe(true);
expect(findDropdownButtonIdentIconAtIndex(0).exists()).toBe(false);
});
});
expect(wrapper.emitted().selected).toEqual([[[projects[0]]], [[]]]);
it("renders an identicon in the dropdown button when the project doesn't have an avatar_url", async () => {
selectDropdownItemAtIndex(1);
await wrapper.vm.$nextTick().then(() => {
expect(
findDropdownButton()
.find('img.gl-avatar')
.exists(),
).toBe(false);
expect(findDropdownButtonIdentIconAtIndex(1).exists()).toBe(true);
});
});
});
});
describe('when using the GraphQl API', () => {
beforeEach(() => {
createComponent({ multiSelect: false, useGraphql: true });
});
describe('displays the correct information', () => {
it('contains 3 items', () => {
expect(findDropdownItems()).toHaveLength(3);
});
it('renders an avatar in the dropdown button when the project has an avatar_url', () => {
selectDropdownItemAtIndex(0);
return wrapper.vm.$nextTick().then(() => {
expect(
findDropdownButton()
.find('img.gl-avatar')
.exists(),
).toBe(true);
expect(
findDropdownButton()
.find('.gl-avatar-identicon')
.exists(),
).toBe(false);
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 an identicon in the dropdown button when the project doesn't have an avatar_url", () => {
selectDropdownItemAtIndex(1);
return wrapper.vm.$nextTick().then(() => {
expect(
findDropdownButton()
.find('img.gl-avatar')
.exists(),
).toBe(false);
expect(
findDropdownButton()
.find('.gl-avatar-identicon')
.exists(),
).toBe(true);
describe('on project click', () => {
it('should emit the "selected" event with the selected project', () => {
selectDropdownItemAtIndex(0);
expect(wrapper.emitted().selected).toEqual([[[mockGraphqlProjects[0]]]]);
});
it('should change selection when new project is clicked', () => {
selectDropdownItemAtIndex(1);
expect(wrapper.emitted().selected).toEqual([[[mockGraphqlProjects[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([[[mockGraphqlProjects[0]]], [[]]]);
});
it('renders an avatar in the dropdown button when the project has an avatarUrl', async () => {
selectDropdownItemAtIndex(0);
await wrapper.vm.$nextTick().then(() => {
expect(
findDropdownButton()
.find('img.gl-avatar')
.exists(),
).toBe(true);
expect(findDropdownButtonIdentIconAtIndex(0).exists()).toBe(false);
});
});
it("renders an identicon in the dropdown button when the project doesn't have an avatarUrl", async () => {
selectDropdownItemAtIndex(1);
await wrapper.vm.$nextTick().then(() => {
expect(
findDropdownButton()
.find('img.gl-avatar')
.exists(),
).toBe(false);
expect(findDropdownButtonIdentIconAtIndex(1).exists()).toBe(true);
});
});
});
});
});
describe('when multiSelect is true', () => {
beforeEach(() => {
createComponent({ multiSelect: true });
});
describe('when using the RESTP API', () => {
beforeEach(() => {
Api.groupProjects.mockImplementation((groupId, term, options, callback) => {
callback(projects);
});
describe('displays the correct information', () => {
it('contains 3 items', () => {
expect(findDropdownItems()).toHaveLength(3);
createComponent({ multiSelect: true });
});
it('renders an avatar when the project has an avatar_url', () => {
expect(
findDropdownAtIndex(0)
.find('img.gl-avatar')
.exists(),
).toBe(true);
expect(
findDropdownAtIndex(0)
.find('div.gl-avatar-identicon')
.exists(),
).toBe(false);
describe('displays the correct information', () => {
it('contains 3 items', () => {
expect(findDropdownItems()).toHaveLength(3);
});
it('renders an avatar when the project has an avatar_url', () => {
expect(findDropdownButtonAvatarAtIndex(0).exists()).toBe(true);
expect(findDropdownButtonIdentIconAtIndex(0).exists()).toBe(false);
});
it("renders an identicon when the project doesn't have an avatar_url", () => {
expect(findDropdownButtonAvatarAtIndex(1).exists()).toBe(false);
expect(findDropdownButtonIdentIconAtIndex(1).exists()).toBe(true);
});
});
it("renders an identicon when the project doesn't have an avatar_url", () => {
expect(
findDropdownAtIndex(1)
.find('img.gl-avatar')
.exists(),
).toBe(false);
expect(
findDropdownAtIndex(1)
.find('div.gl-avatar-identicon')
.exists(),
).toBe(true);
describe('on project click', () => {
it('should add to selection when new project is clicked', () => {
selectDropdownItemAtIndex(0);
selectDropdownItemAtIndex(1);
expect(wrapper.emitted().selected).toEqual([
[[projects[0]]],
[[projects[0], projects[1]]],
]);
});
it('should remove from selection when clicked again', () => {
selectDropdownItemAtIndex(0);
selectDropdownItemAtIndex(0);
expect(wrapper.emitted().selected).toEqual([[[projects[0]]], [[]]]);
});
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');
});
});
});
});
describe('on project click', () => {
it('should add to selection when new project is clicked', () => {
selectDropdownItemAtIndex(0);
selectDropdownItemAtIndex(1);
expect(wrapper.emitted().selected).toEqual([[[projects[0]]], [[projects[0], projects[1]]]]);
describe('when using the GraphQl API', () => {
beforeEach(() => {
createComponent({ multiSelect: true, useGraphql: true });
});
it('should remove from selection when clicked again', () => {
selectDropdownItemAtIndex(0);
selectDropdownItemAtIndex(0);
describe('displays the correct information', () => {
it('contains 3 items', () => {
expect(findDropdownItems()).toHaveLength(3);
});
expect(wrapper.emitted().selected).toEqual([[[projects[0]]], [[]]]);
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 correct placeholder text when multiple projects are selected', () => {
selectDropdownItemAtIndex(0);
selectDropdownItemAtIndex(1);
describe('on project click', () => {
it('should add to selection when new project is clicked', () => {
selectDropdownItemAtIndex(0);
selectDropdownItemAtIndex(1);
expect(wrapper.emitted().selected).toEqual([
[[mockGraphqlProjects[0]]],
[[mockGraphqlProjects[0], mockGraphqlProjects[1]]],
]);
});
it('should remove from selection when clicked again', () => {
selectDropdownItemAtIndex(0);
selectDropdownItemAtIndex(0);
expect(wrapper.emitted().selected).toEqual([[[mockGraphqlProjects[0]]], [[]]]);
});
it('renders the correct placeholder text when multiple projects are selected', async () => {
selectDropdownItemAtIndex(0);
selectDropdownItemAtIndex(1);
return wrapper.vm.$nextTick().then(() => {
expect(findDropdownButton().text()).toBe('2 projects selected');
await wrapper.vm.$nextTick().then(() => {
expect(findDropdownButton().text()).toBe('2 projects selected');
});
});
});
});
......
......@@ -22,6 +22,7 @@ const subGroupDataset = {
const projectDataset = {
projectId: '1',
projectGid: 'gid://gitlab/Project/1',
projectName: 'My Project',
projectPathWithNamespace: 'my-group/my-project',
};
......@@ -66,7 +67,7 @@ describe('buildProjectFromDataset', () => {
it('returns a project object when the projectId is given', () => {
expect(buildProjectFromDataset(projectDataset)).toEqual({
id: 1,
id: 'gid://gitlab/Project/1',
name: 'My Project',
path_with_namespace: 'my-group/my-project',
avatar_url: undefined,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment