Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/diffs/components/diff_file_spec.js160
-rw-r--r--spec/frontend/projects/commits/components/author_select_spec.js110
-rw-r--r--spec/frontend/repository/components/breadcrumbs_spec.js43
-rw-r--r--spec/frontend/super_sidebar/components/help_center_spec.js6
-rw-r--r--spec/frontend/tracking/internal_events_spec.js63
5 files changed, 299 insertions, 83 deletions
diff --git a/spec/frontend/diffs/components/diff_file_spec.js b/spec/frontend/diffs/components/diff_file_spec.js
index 72e1149a01c..9b9a1a84b1d 100644
--- a/spec/frontend/diffs/components/diff_file_spec.js
+++ b/spec/frontend/diffs/components/diff_file_spec.js
@@ -1,7 +1,11 @@
-import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+
+import waitForPromises from 'helpers/wait_for_promises';
+import { sprintf } from '~/locale';
+import { createAlert } from '~/alert';
import DiffContentComponent from 'jh_else_ce/diffs/components/diff_content.vue';
import DiffFileComponent from '~/diffs/components/diff_file.vue';
@@ -11,19 +15,33 @@ import {
EVT_EXPAND_ALL_FILES,
EVT_PERF_MARK_DIFF_FILES_END,
EVT_PERF_MARK_FIRST_DIFF_FILE_SHOWN,
+ FILE_DIFF_POSITION_TYPE,
} from '~/diffs/constants';
import eventHub from '~/diffs/event_hub';
-import createDiffsStore from '~/diffs/store/modules';
import { diffViewerModes, diffViewerErrors } from '~/ide/constants';
import axios from '~/lib/utils/axios_utils';
import { scrollToElement } from '~/lib/utils/common_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import createNotesStore from '~/notes/stores/modules';
+import diffsModule from '~/diffs/store/modules';
+import { SOMETHING_WENT_WRONG, SAVING_THE_COMMENT_FAILED } from '~/diffs/i18n';
+import diffLineNoteFormMixin from '~/notes/mixins/diff_line_note_form';
import { getDiffFileMock } from '../mock_data/diff_file';
import diffFileMockDataUnreadable from '../mock_data/diff_file_unreadable';
+import diffsMockData from '../mock_data/merge_request_diffs';
jest.mock('~/lib/utils/common_utils');
+jest.mock('~/alert');
+jest.mock('~/notes/mixins/diff_line_note_form', () => ({
+ methods: {
+ addToReview: jest.fn(),
+ },
+}));
+
+Vue.use(Vuex);
+
+const saveDiffDiscussionMock = jest.fn();
function changeViewer(store, index, { automaticallyCollapsed, manuallyCollapsed, name }) {
const file = store.state.diffs.diffFiles[index];
@@ -70,18 +88,29 @@ function markFileToBeRendered(store, index = 0) {
}
function createComponent({ file, first = false, last = false, options = {}, props = {} }) {
- Vue.use(Vuex);
+ const diffs = diffsModule();
+ diffs.actions = {
+ ...diffs.actions,
+ saveDiffDiscussion: saveDiffDiscussionMock,
+ };
+
+ diffs.getters = {
+ ...diffs.getters,
+ diffCompareDropdownTargetVersions: () => [],
+ diffCompareDropdownSourceVersions: () => [],
+ };
const store = new Vuex.Store({
...createNotesStore(),
- modules: {
- diffs: createDiffsStore(),
- },
+ modules: { diffs },
});
- store.state.diffs.diffFiles = [file];
+ store.state.diffs = {
+ mergeRequestDiff: diffsMockData[0],
+ diffFiles: [file],
+ };
- const wrapper = shallowMount(DiffFileComponent, {
+ const wrapper = shallowMountExtended(DiffFileComponent, {
store,
propsData: {
file,
@@ -101,9 +130,10 @@ function createComponent({ file, first = false, last = false, options = {}, prop
}
const findDiffHeader = (wrapper) => wrapper.findComponent(DiffFileHeaderComponent);
-const findDiffContentArea = (wrapper) => wrapper.find('[data-testid="content-area"]');
-const findLoader = (wrapper) => wrapper.find('[data-testid="loader-icon"]');
-const findToggleButton = (wrapper) => wrapper.find('[data-testid="expand-button"]');
+const findDiffContentArea = (wrapper) => wrapper.findByTestId('content-area');
+const findLoader = (wrapper) => wrapper.findByTestId('loader-icon');
+const findToggleButton = (wrapper) => wrapper.findByTestId('expand-button');
+const findNoteForm = (wrapper) => wrapper.findByTestId('file-note-form');
const toggleFile = (wrapper) => findDiffHeader(wrapper).vm.$emit('toggleFile');
const getReadableFile = () => getDiffFileMock();
@@ -118,6 +148,12 @@ const makeFileManuallyCollapsed = (store, index = 0) =>
const changeViewerType = (store, newType, index = 0) =>
changeViewer(store, index, { name: diffViewerModes[newType] });
+const triggerSaveNote = (wrapper, note, parent, error) =>
+ findNoteForm(wrapper).vm.$emit('handleFormUpdate', note, parent, error);
+
+const triggerSaveDraftNote = (wrapper, note, parent, error) =>
+ findNoteForm(wrapper).vm.$emit('handleFormUpdateAddToReview', note, false, parent, error);
+
describe('DiffFile', () => {
let wrapper;
let store;
@@ -502,7 +538,7 @@ describe('DiffFile', () => {
await nextTick();
- const button = wrapper.find('[data-testid="blob-button"]');
+ const button = wrapper.findByTestId('blob-button');
expect(wrapper.text()).toContain('Changes are too large to be shown.');
expect(button.html()).toContain('View file @');
@@ -538,7 +574,7 @@ describe('DiffFile', () => {
({ wrapper, store } = createComponent({ file }));
- expect(wrapper.find('[data-testid="conflictsAlert"]').exists()).toBe(false);
+ expect(wrapper.findByTestId('conflictsAlert').exists()).toBe(false);
});
it('renders conflict alert when conflict_type is present', () => {
@@ -550,7 +586,7 @@ describe('DiffFile', () => {
({ wrapper, store } = createComponent({ file }));
- expect(wrapper.find('[data-testid="conflictsAlert"]').exists()).toBe(true);
+ expect(wrapper.findByTestId('conflictsAlert').exists()).toBe(true);
});
});
@@ -574,7 +610,7 @@ describe('DiffFile', () => {
file,
}));
- expect(wrapper.find('[data-testid="file-discussions"]').exists()).toEqual(exists);
+ expect(wrapper.findByTestId('file-discussions').exists()).toEqual(exists);
},
);
@@ -594,7 +630,7 @@ describe('DiffFile', () => {
file,
}));
- expect(wrapper.find('[data-testid="file-note-form"]').exists()).toEqual(exists);
+ expect(findNoteForm(wrapper).exists()).toEqual(exists);
},
);
@@ -612,7 +648,97 @@ describe('DiffFile', () => {
file,
}));
- expect(wrapper.find('[data-testid="diff-file-discussions"]').exists()).toEqual(exists);
+ expect(wrapper.findByTestId('diff-file-discussions').exists()).toEqual(exists);
+ });
+
+ describe('when note-form emits `handleFormUpdate`', () => {
+ const file = {
+ ...getReadableFile(),
+ hasCommentForm: true,
+ };
+
+ const note = {};
+ const parentElement = null;
+ const errorCallback = jest.fn();
+
+ beforeEach(() => {
+ ({ wrapper, store } = createComponent({
+ file,
+ options: { provide: { glFeatures: { commentOnFiles: true } } },
+ }));
+ });
+
+ it('calls saveDiffDiscussionMock', () => {
+ triggerSaveNote(wrapper, note, parentElement, errorCallback);
+
+ expect(saveDiffDiscussionMock).toHaveBeenCalledWith(expect.any(Object), {
+ note,
+ formData: {
+ noteableData: expect.any(Object),
+ diffFile: file,
+ positionType: FILE_DIFF_POSITION_TYPE,
+ noteableType: store.getters.noteableType,
+ },
+ });
+ });
+
+ describe('when saveDiffDiscussionMock throws an error', () => {
+ describe.each`
+ scenario | serverError | message
+ ${'with server error'} | ${{ data: { errors: 'error' } }} | ${SAVING_THE_COMMENT_FAILED}
+ ${'without server error'} | ${{}} | ${SOMETHING_WENT_WRONG}
+ `('$scenario', ({ serverError, message }) => {
+ beforeEach(async () => {
+ saveDiffDiscussionMock.mockRejectedValue({ response: serverError });
+
+ triggerSaveNote(wrapper, note, parentElement, errorCallback);
+
+ await waitForPromises();
+ });
+
+ it(`renders ${serverError ? 'server' : 'generic'} error message`, () => {
+ expect(createAlert).toHaveBeenCalledWith({
+ message: sprintf(message, { reason: serverError?.data?.errors }),
+ parent: parentElement,
+ });
+ });
+
+ it('calls errorCallback', () => {
+ expect(errorCallback).toHaveBeenCalled();
+ });
+ });
+ });
+ });
+
+ describe('when note-form emits `handleFormUpdateAddToReview`', () => {
+ const file = {
+ ...getReadableFile(),
+ hasCommentForm: true,
+ };
+
+ const note = {};
+ const parentElement = null;
+ const errorCallback = jest.fn();
+
+ beforeEach(async () => {
+ ({ wrapper, store } = createComponent({
+ file,
+ options: { provide: { glFeatures: { commentOnFiles: true } } },
+ }));
+
+ triggerSaveDraftNote(wrapper, note, parentElement, errorCallback);
+
+ await nextTick();
+ });
+
+ it('calls addToReview mixin', () => {
+ expect(diffLineNoteFormMixin.methods.addToReview).toHaveBeenCalledWith(
+ note,
+ FILE_DIFF_POSITION_TYPE,
+ parentElement,
+ errorCallback,
+ );
+ });
});
});
});
diff --git a/spec/frontend/projects/commits/components/author_select_spec.js b/spec/frontend/projects/commits/components/author_select_spec.js
index 630b8feafbc..50e3f2d0f37 100644
--- a/spec/frontend/projects/commits/components/author_select_spec.js
+++ b/spec/frontend/projects/commits/components/author_select_spec.js
@@ -1,12 +1,17 @@
-import { GlDropdown, GlDropdownSectionHeader, GlSearchBoxByType, GlDropdownItem } from '@gitlab/ui';
+import { GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
-import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
+import { resetHTMLFixture, setHTMLFixture } from 'helpers/fixtures';
import setWindowLocation from 'helpers/set_window_location_helper';
-import * as urlUtility from '~/lib/utils/url_utility';
import AuthorSelect from '~/projects/commits/components/author_select.vue';
import { createStore } from '~/projects/commits/store';
+import { visitUrl } from '~/lib/utils/url_utility';
+
+jest.mock('~/lib/utils/url_utility', () => ({
+ ...jest.requireActual('~/lib/utils/url_utility'),
+ visitUrl: jest.fn(),
+}));
Vue.use(Vuex);
@@ -44,6 +49,10 @@ describe('Author Select', () => {
propsData: {
projectCommitsEl: document.querySelector('.js-project-commits-show'),
},
+ stubs: {
+ GlCollapsibleListbox,
+ GlListboxItem,
+ },
});
};
@@ -58,11 +67,9 @@ describe('Author Select', () => {
resetHTMLFixture();
});
- const findDropdownContainer = () => wrapper.findComponent({ ref: 'dropdownContainer' });
- const findDropdown = () => wrapper.findComponent(GlDropdown);
- const findDropdownHeader = () => wrapper.findComponent(GlDropdownSectionHeader);
- const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
- const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
+ const findListboxContainer = () => wrapper.findComponent({ ref: 'listboxContainer' });
+ const findListbox = () => wrapper.findComponent(GlCollapsibleListbox);
+ const findListboxItems = () => wrapper.findAllComponents(GlListboxItem);
describe('user is searching via "filter by commit message"', () => {
beforeEach(() => {
@@ -70,24 +77,28 @@ describe('Author Select', () => {
createComponent();
});
- it('does not disable dropdown container', () => {
- expect(findDropdownContainer().attributes('disabled')).toBeUndefined();
+ it('does not disable listbox container', () => {
+ expect(findListboxContainer().attributes('disabled')).toBeUndefined();
});
it('has correct tooltip message', () => {
- expect(findDropdownContainer().attributes('title')).toBe(
+ expect(findListboxContainer().attributes('title')).toBe(
'Searching by both author and message is currently not supported.',
);
});
- it('disables dropdown', () => {
- expect(findDropdown().attributes('disabled')).toBeDefined();
+ it('disables listbox', () => {
+ expect(findListbox().attributes('disabled')).toBeDefined();
});
});
- describe('dropdown', () => {
+ describe('listbox', () => {
+ beforeEach(() => {
+ store.state.commitsPath = commitsPath;
+ });
+
it('displays correct default text', () => {
- expect(findDropdown().attributes('text')).toBe('Author');
+ expect(findListbox().props('toggleText')).toBe('Author');
});
it('displays the current selected author', async () => {
@@ -95,81 +106,62 @@ describe('Author Select', () => {
createComponent();
await nextTick();
- expect(findDropdown().attributes('text')).toBe(currentAuthor);
+ expect(findListbox().props('toggleText')).toBe(currentAuthor);
});
it('displays correct header text', () => {
- expect(findDropdownHeader().text()).toBe('Search by author');
+ expect(findListbox().props('headerText')).toBe('Search by author');
});
it('does not have popover text by default', () => {
expect(wrapper.attributes('title')).toBeUndefined();
});
+
+ it('passes selected author to redirectPath', () => {
+ const redirectPath = `${commitsPath}?author=${currentAuthor}`;
+
+ findListbox().vm.$emit('select', currentAuthor);
+
+ expect(visitUrl).toHaveBeenCalledWith(redirectPath);
+ });
+
+ it('does not pass any author to redirectPath', () => {
+ const redirectPath = commitsPath;
+
+ findListbox().vm.$emit('select', '');
+
+ expect(visitUrl).toHaveBeenCalledWith(redirectPath);
+ });
});
- describe('dropdown search box', () => {
+ describe('listbox search box', () => {
it('has correct placeholder', () => {
- expect(findSearchBox().attributes('placeholder')).toBe('Search');
+ expect(findListbox().props('searchPlaceholder')).toBe('Search');
});
it('fetch authors on input change', () => {
const authorName = 'lorem';
- findSearchBox().vm.$emit('input', authorName);
+ findListbox().vm.$emit('search', authorName);
expect(store.actions.fetchAuthors).toHaveBeenCalledWith(expect.anything(), authorName);
});
});
- describe('dropdown list', () => {
+ describe('listbox list', () => {
beforeEach(() => {
store.state.commitsAuthors = authors;
- store.state.commitsPath = commitsPath;
});
it('has a "Any Author" as the first list item', () => {
- expect(findDropdownItems().at(0).text()).toBe('Any Author');
+ expect(findListboxItems().at(0).text()).toBe('Any Author');
});
it('displays the project authors', () => {
- expect(findDropdownItems()).toHaveLength(authors.length + 1);
- });
-
- it('has the correct props', async () => {
- setWindowLocation(`?author=${currentAuthor}`);
- createComponent();
-
- const [{ avatar_url: avatarUrl, username }] = authors;
- const result = {
- avatarUrl,
- secondaryText: username,
- isChecked: true,
- };
-
- await nextTick();
- expect(findDropdownItems().at(1).props()).toEqual(expect.objectContaining(result));
+ expect(findListboxItems()).toHaveLength(authors.length + 1);
});
it("display the author's name", () => {
- expect(findDropdownItems().at(1).text()).toBe(currentAuthor);
- });
-
- it('passes selected author to redirectPath', () => {
- const redirectToUrl = `${commitsPath}?author=${currentAuthor}`;
- const spy = jest.spyOn(urlUtility, 'redirectTo');
- spy.mockImplementation(() => 'mock');
-
- findDropdownItems().at(1).vm.$emit('click');
-
- expect(spy).toHaveBeenCalledWith(redirectToUrl);
- });
-
- it('does not pass any author to redirectPath', () => {
- const redirectToUrl = commitsPath;
- const spy = jest.spyOn(urlUtility, 'redirectTo');
- spy.mockImplementation();
-
- findDropdownItems().at(0).vm.$emit('click');
- expect(spy).toHaveBeenCalledWith(redirectToUrl);
+ expect(findListboxItems().at(1).text()).toContain(currentAuthor);
});
});
});
diff --git a/spec/frontend/repository/components/breadcrumbs_spec.js b/spec/frontend/repository/components/breadcrumbs_spec.js
index f4baa817d32..46a7f2ee1bb 100644
--- a/spec/frontend/repository/components/breadcrumbs_spec.js
+++ b/spec/frontend/repository/components/breadcrumbs_spec.js
@@ -1,6 +1,6 @@
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
-import { GlDropdown } from '@gitlab/ui';
+import { GlDisclosureDropdown, GlDisclosureDropdownGroup } from '@gitlab/ui';
import { shallowMount, RouterLinkStub } from '@vue/test-utils';
import Breadcrumbs from '~/repository/components/breadcrumbs.vue';
import UploadBlobModal from '~/repository/components/upload_blob_modal.vue';
@@ -11,6 +11,7 @@ import permissionsQuery from 'shared_queries/repository/permissions.query.graphq
import projectPathQuery from '~/repository/queries/project_path.query.graphql';
import createApolloProvider from 'helpers/mock_apollo_helper';
+import { __ } from '~/locale';
const defaultMockRoute = {
name: 'blobPath',
@@ -61,6 +62,7 @@ describe('Repository breadcrumbs component', () => {
},
stubs: {
RouterLink: RouterLinkStub,
+ GlDisclosureDropdown,
},
mocks: {
$route: {
@@ -71,7 +73,8 @@ describe('Repository breadcrumbs component', () => {
});
};
- const findDropdown = () => wrapper.findComponent(GlDropdown);
+ const findDropdown = () => wrapper.findComponent(GlDisclosureDropdown);
+ const findDropdownGroup = () => wrapper.findComponent(GlDisclosureDropdownGroup);
const findUploadBlobModal = () => wrapper.findComponent(UploadBlobModal);
const findNewDirectoryModal = () => wrapper.findComponent(NewDirectoryModal);
const findRouterLink = () => wrapper.findAllComponents(RouterLinkStub);
@@ -146,7 +149,11 @@ describe('Repository breadcrumbs component', () => {
`(
'does render add to tree dropdown $isRendered when route is $routeName',
({ routeName, isRendered }) => {
- factory('app/assets/javascripts.js', { canCollaborate: true }, { name: routeName });
+ factory(
+ 'app/assets/javascripts.js',
+ { canCollaborate: true, canEditTree: true },
+ { name: routeName },
+ );
expect(findDropdown().exists()).toBe(isRendered);
},
);
@@ -156,7 +163,7 @@ describe('Repository breadcrumbs component', () => {
createPermissionsQueryResponse({ forkProject: true, createMergeRequestIn: true }),
);
- factory('/', { canCollaborate: true });
+ factory('/', { canCollaborate: true, canEditTree: true });
await nextTick();
expect(findDropdown().exists()).toBe(true);
@@ -193,4 +200,32 @@ describe('Repository breadcrumbs component', () => {
expect(findNewDirectoryModal().props('path')).toBe('root/master/some_dir');
});
});
+
+ describe('"this repository" dropdown group', () => {
+ it('renders when user has pushCode permissions', async () => {
+ permissionsQuerySpy.mockResolvedValue(
+ createPermissionsQueryResponse({
+ pushCode: true,
+ }),
+ );
+
+ factory('/', { canCollaborate: true });
+ await waitForPromises();
+
+ expect(findDropdownGroup().props('group').name).toBe(__('This repository'));
+ });
+
+ it('does not render when user does not have pushCode permissions', async () => {
+ permissionsQuerySpy.mockResolvedValue(
+ createPermissionsQueryResponse({
+ pushCode: false,
+ }),
+ );
+
+ factory('/', { canCollaborate: true });
+ await waitForPromises();
+
+ expect(findDropdownGroup().exists()).toBe(false);
+ });
+ });
});
diff --git a/spec/frontend/super_sidebar/components/help_center_spec.js b/spec/frontend/super_sidebar/components/help_center_spec.js
index 6af1172e4d8..c92f8a68678 100644
--- a/spec/frontend/super_sidebar/components/help_center_spec.js
+++ b/spec/frontend/super_sidebar/components/help_center_spec.js
@@ -104,7 +104,7 @@ describe('HelpCenter component', () => {
createWrapper({ ...sidebarData, show_tanuki_bot: true });
});
- it('shows Ask GitLab Chat with the help items', () => {
+ it('shows Ask GitLab Duo with the help items', () => {
expect(findDropdownGroup(0).props('group').items).toEqual([
expect.objectContaining({
icon: 'tanuki-ai',
@@ -115,9 +115,9 @@ describe('HelpCenter component', () => {
]);
});
- describe('when Ask GitLab Chat button is clicked', () => {
+ describe('when Ask GitLab Duo button is clicked', () => {
beforeEach(() => {
- findButton('Ask GitLab Chat').click();
+ findButton('Ask GitLab Duo').click();
});
it('sets helpCenterState.showTanukiBotChatDrawer to true', () => {
diff --git a/spec/frontend/tracking/internal_events_spec.js b/spec/frontend/tracking/internal_events_spec.js
new file mode 100644
index 00000000000..2179b2e489e
--- /dev/null
+++ b/spec/frontend/tracking/internal_events_spec.js
@@ -0,0 +1,63 @@
+import API from '~/api';
+import { mockTracking } from 'helpers/tracking_helper';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import InternalEvents from '~/tracking/internal_events';
+import { GITLAB_INTERNAL_EVENT_CATEGORY, SERVICE_PING_SCHEMA } from '~/tracking/constants';
+
+jest.mock('~/api', () => ({
+ trackRedisHllUserEvent: jest.fn(),
+}));
+
+describe('InternalEvents', () => {
+ describe('track_event', () => {
+ it('track_event calls trackRedisHllUserEvent with correct arguments', () => {
+ const event = 'TestEvent';
+
+ InternalEvents.track_event(event);
+
+ expect(API.trackRedisHllUserEvent).toHaveBeenCalledTimes(1);
+ expect(API.trackRedisHllUserEvent).toHaveBeenCalledWith(event);
+ });
+
+ it('track_event calls tracking.event functions with correct arguments', () => {
+ const trackingSpy = mockTracking(GITLAB_INTERNAL_EVENT_CATEGORY, undefined, jest.spyOn);
+
+ const event = 'TestEvent';
+
+ InternalEvents.track_event(event);
+
+ expect(trackingSpy).toHaveBeenCalledTimes(1);
+ expect(trackingSpy).toHaveBeenCalledWith(GITLAB_INTERNAL_EVENT_CATEGORY, event, {
+ context: {
+ schema: SERVICE_PING_SCHEMA,
+ data: {
+ event_name: event,
+ data_source: 'redis_hll',
+ },
+ },
+ });
+ });
+ });
+
+ describe('mixin', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ const Component = {
+ render() {},
+ mixins: [InternalEvents.mixin()],
+ };
+ wrapper = shallowMountExtended(Component);
+ });
+
+ it('this.track_event function calls InternalEvent`s track function with an event', () => {
+ const event = 'TestEvent';
+ const trackEventSpy = jest.spyOn(InternalEvents, 'track_event');
+
+ wrapper.vm.track_event(event);
+
+ expect(trackEventSpy).toHaveBeenCalledTimes(1);
+ expect(trackEventSpy).toHaveBeenCalledWith(event);
+ });
+ });
+});