gitlab-org--gitlab-foss/spec/frontend/search_autocomplete_spec.js

293 lines
9.1 KiB
JavaScript

import AxiosMockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import axios from '~/lib/utils/axios_utils';
import initSearchAutocomplete from '~/search_autocomplete';
import '~/lib/utils/common_utils';
describe('Search autocomplete dropdown', () => {
let widget = null;
const userName = 'root';
const userId = 1;
const dashboardIssuesPath = '/dashboard/issues';
const dashboardMRsPath = '/dashboard/merge_requests';
const projectIssuesPath = '/gitlab-org/gitlab-foss/issues';
const projectMRsPath = '/gitlab-org/gitlab-foss/-/merge_requests';
const groupIssuesPath = '/groups/gitlab-org/-/issues';
const groupMRsPath = '/groups/gitlab-org/-/merge_requests';
const autocompletePath = '/search/autocomplete';
const projectName = 'GitLab Community Edition';
const groupName = 'Gitlab Org';
const removeBodyAttributes = () => {
const { body } = document;
delete body.dataset.page;
delete body.dataset.project;
delete body.dataset.group;
};
// Add required attributes to body before starting the test.
// section would be dashboard|group|project
const addBodyAttributes = (section = 'dashboard') => {
removeBodyAttributes();
const { body } = document;
switch (section) {
case 'dashboard':
body.dataset.page = 'root:index';
break;
case 'group':
body.dataset.page = 'groups:show';
body.dataset.group = 'gitlab-org';
break;
case 'project':
body.dataset.page = 'projects:show';
body.dataset.project = 'gitlab-ce';
break;
default:
break;
}
};
const disableProjectIssues = () => {
document.querySelector('.js-search-project-options').dataset.issuesDisabled = true;
};
// Mock `gl` object in window for dashboard specific page. App code will need it.
const mockDashboardOptions = () => {
window.gl.dashboardOptions = {
issuesPath: dashboardIssuesPath,
mrPath: dashboardMRsPath,
};
};
// Mock `gl` object in window for project specific page. App code will need it.
const mockProjectOptions = () => {
window.gl.projectOptions = {
'gitlab-ce': {
issuesPath: projectIssuesPath,
mrPath: projectMRsPath,
projectName,
},
};
};
const mockGroupOptions = () => {
window.gl.groupOptions = {
'gitlab-org': {
issuesPath: groupIssuesPath,
mrPath: groupMRsPath,
projectName: groupName,
},
};
};
const assertLinks = (list, issuesPath, mrsPath) => {
if (issuesPath) {
const issuesAssignedToMeLink = `a[href="${issuesPath}/?assignee_username=${userName}"]`;
const issuesIHaveCreatedLink = `a[href="${issuesPath}/?author_username=${userName}"]`;
expect(list.find(issuesAssignedToMeLink).length).toBe(1);
expect(list.find(issuesAssignedToMeLink).text()).toBe('Issues assigned to me');
expect(list.find(issuesIHaveCreatedLink).length).toBe(1);
expect(list.find(issuesIHaveCreatedLink).text()).toBe("Issues I've created");
}
const mrsAssignedToMeLink = `a[href="${mrsPath}/?assignee_username=${userName}"]`;
const mrsIHaveCreatedLink = `a[href="${mrsPath}/?author_username=${userName}"]`;
expect(list.find(mrsAssignedToMeLink).length).toBe(1);
expect(list.find(mrsAssignedToMeLink).text()).toBe('Merge requests assigned to me');
expect(list.find(mrsIHaveCreatedLink).length).toBe(1);
expect(list.find(mrsIHaveCreatedLink).text()).toBe("Merge requests I've created");
};
beforeEach(() => {
loadHTMLFixture('static/search_autocomplete.html');
window.gon = {};
window.gon.current_user_id = userId;
window.gon.current_username = userName;
window.gl = window.gl || (window.gl = {});
widget = initSearchAutocomplete({ autocompletePath });
});
afterEach(() => {
// Undo what we did to the shared <body>
removeBodyAttributes();
window.gon = {};
resetHTMLFixture();
});
it('should show Dashboard specific dropdown menu', () => {
addBodyAttributes();
mockDashboardOptions();
widget.searchInput.triggerHandler('focus');
const list = widget.wrap.find('.dropdown-menu').find('ul');
return assertLinks(list, dashboardIssuesPath, dashboardMRsPath);
});
it('should show Group specific dropdown menu', () => {
addBodyAttributes('group');
mockGroupOptions();
widget.searchInput.triggerHandler('focus');
const list = widget.wrap.find('.dropdown-menu').find('ul');
return assertLinks(list, groupIssuesPath, groupMRsPath);
});
it('should show Project specific dropdown menu', () => {
addBodyAttributes('project');
mockProjectOptions();
widget.searchInput.triggerHandler('focus');
const list = widget.wrap.find('.dropdown-menu').find('ul');
return assertLinks(list, projectIssuesPath, projectMRsPath);
});
it('should show only Project mergeRequest dropdown menu items when project issues are disabled', () => {
addBodyAttributes('project');
disableProjectIssues();
mockProjectOptions();
widget.searchInput.triggerHandler('focus');
const list = widget.wrap.find('.dropdown-menu').find('ul');
assertLinks(list, null, projectMRsPath);
});
it('should not show category related menu if there is text in the input', () => {
addBodyAttributes('project');
mockProjectOptions();
widget.searchInput.val('help');
widget.searchInput.triggerHandler('focus');
const list = widget.wrap.find('.dropdown-menu').find('ul');
const link = `a[href='${projectIssuesPath}/?assignee_username=${userName}']`;
expect(list.find(link).length).toBe(0);
});
it('should not submit the search form when selecting an autocomplete row with the keyboard', () => {
const ENTER = 13;
const DOWN = 40;
addBodyAttributes();
mockDashboardOptions(true);
const submitSpy = jest.spyOn(document.querySelector('form'), 'submit');
widget.searchInput.triggerHandler('focus');
widget.wrap.trigger($.Event('keydown', { which: DOWN }));
const enterKeyEvent = $.Event('keydown', { which: ENTER });
widget.searchInput.trigger(enterKeyEvent);
// This does not currently catch failing behavior. For security reasons,
// browsers will not trigger default behavior (form submit, in this
// example) on JavaScript-created keypresses.
expect(submitSpy).not.toHaveBeenCalled();
});
describe('show autocomplete results', () => {
beforeEach(() => {
widget.enableAutocomplete();
const axiosMock = new AxiosMockAdapter(axios);
const autocompleteUrl = new RegExp(autocompletePath);
axiosMock.onGet(autocompleteUrl).reply(200, [
{
category: 'Projects',
id: 1,
value: 'Gitlab Test',
label: 'Gitlab Org / Gitlab Test',
url: '/gitlab-org/gitlab-test',
avatar_url: '',
},
{
category: 'Groups',
id: 1,
value: 'Gitlab Org',
label: 'Gitlab Org',
url: '/gitlab-org',
avatar_url: '',
},
]);
});
function triggerAutocomplete() {
return new Promise((resolve) => {
const dropdown = widget.searchInput.data('deprecatedJQueryDropdown');
const filterCallback = dropdown.filter.options.callback;
dropdown.filter.options.callback = jest.fn((data) => {
filterCallback(data);
resolve();
});
widget.searchInput.val('Gitlab');
widget.searchInput.triggerHandler('input');
});
}
it('suggest Projects', async () => {
await triggerAutocomplete();
const list = widget.wrap.find('.dropdown-menu').find('ul');
const link = "a[href$='/gitlab-org/gitlab-test']";
expect(list.find(link).length).toBe(1);
});
it('suggest Groups', async () => {
await triggerAutocomplete();
const list = widget.wrap.find('.dropdown-menu').find('ul');
const link = "a[href$='/gitlab-org']";
expect(list.find(link).length).toBe(1);
});
});
describe('disableAutocomplete', () => {
beforeEach(() => {
widget.enableAutocomplete();
});
it('should close the Dropdown', () => {
const toggleSpy = jest.spyOn(widget.dropdownToggle, 'dropdown');
widget.dropdown.addClass('show');
widget.disableAutocomplete();
expect(toggleSpy).toHaveBeenCalledWith('toggle');
});
});
describe('enableAutocomplete', () => {
let toggleSpy;
let trackingSpy;
beforeEach(() => {
toggleSpy = jest.spyOn(widget.dropdownToggle, 'dropdown');
trackingSpy = mockTracking('_category_', undefined, jest.spyOn);
document.body.dataset.page = 'some:page'; // default tracking for category
});
afterEach(() => {
unmockTracking();
});
it('should open the Dropdown', () => {
widget.enableAutocomplete();
expect(toggleSpy).toHaveBeenCalledWith('toggle');
});
it('should track the opening', () => {
widget.enableAutocomplete();
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_search_bar', {
label: 'main_navigation',
property: 'navigation',
});
});
});
});