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:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-12-19 14:01:45 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-12-19 14:01:45 +0300
commit9297025d0b7ddf095eb618dfaaab2ff8f2018d8b (patch)
tree865198c01d1824a9b098127baa3ab980c9cd2c06 /spec/frontend/vue_merge_request_widget/components
parent6372471f43ee03c05a7c1f8b0c6ac6b8a7431dbe (diff)
Add latest changes from gitlab-org/gitlab@16-7-stable-eev16.7.0-rc42
Diffstat (limited to 'spec/frontend/vue_merge_request_widget/components')
-rw-r--r--spec/frontend/vue_merge_request_widget/components/approvals/approvals_spec.js36
-rw-r--r--spec/frontend/vue_merge_request_widget/components/checks/draft_spec.js196
-rw-r--r--spec/frontend/vue_merge_request_widget/components/checks/rebase_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/merge_checks_spec.js2
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_expandable_section_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js3
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_status_icon_spec.js7
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/__snapshots__/new_ready_to_merge_spec.js.snap4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_conflicts_spec.js173
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_merging_spec.js23
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge_spec.js8
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js82
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/work_in_progress_spec.js16
-rw-r--r--spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap7
14 files changed, 486 insertions, 79 deletions
diff --git a/spec/frontend/vue_merge_request_widget/components/approvals/approvals_spec.js b/spec/frontend/vue_merge_request_widget/components/approvals/approvals_spec.js
index c81f4328d2a..c3ed131d6e3 100644
--- a/spec/frontend/vue_merge_request_widget/components/approvals/approvals_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/approvals/approvals_spec.js
@@ -1,11 +1,11 @@
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import { GlButton, GlSprintf } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
import { createMockSubscription as createMockApolloSubscription } from 'mock-apollo-client';
import approvedByCurrentUser from 'test_fixtures/graphql/merge_requests/approvals/approvals.query.graphql.json';
-import { visitUrl } from '~/lib/utils/url_utility';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
+import { stubComponent } from 'helpers/stub_component';
import waitForPromises from 'helpers/wait_for_promises';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { createAlert } from '~/alert';
@@ -29,11 +29,6 @@ jest.mock('~/alert', () => ({
dismiss: mockAlertDismiss,
})),
}));
-jest.mock('~/lib/utils/url_utility', () => ({
- ...jest.requireActual('~/lib/utils/url_utility'),
- visitUrl: jest.fn(),
-}));
-
const TEST_HELP_PATH = 'help/path';
const testApprovedBy = () => [1, 7, 10].map((id) => ({ id }));
const testApprovals = () => ({
@@ -53,6 +48,9 @@ describe('MRWidget approvals', () => {
let wrapper;
let service;
let mr;
+ const submitSpy = jest.fn().mockImplementation((e) => {
+ e.preventDefault();
+ });
const createComponent = (options = {}, responses = { query: approvedByCurrentUser }) => {
mockedSubscription = createMockApolloSubscription();
@@ -68,7 +66,7 @@ describe('MRWidget approvals', () => {
apolloProvider.defaultClient.setRequestHandler(query, stream);
});
- wrapper = shallowMount(Approvals, {
+ wrapper = shallowMountExtended(Approvals, {
apolloProvider,
propsData: {
mr,
@@ -78,7 +76,18 @@ describe('MRWidget approvals', () => {
provide,
stubs: {
GlSprintf,
+ GlForm: {
+ data() {
+ return { submitSpy };
+ },
+ // Workaround jsdom not implementing form submit
+ template: '<form @submit="submitSpy"><slot></slot></form>',
+ },
+ GlButton: stubComponent(GlButton, {
+ template: '<button><slot></slot></button>',
+ }),
},
+ attachTo: document.body,
});
};
@@ -257,11 +266,11 @@ describe('MRWidget approvals', () => {
});
describe('when SAML auth is required and user clicks Approve with SAML', () => {
- const fakeGroupSamlPath = '/example_group_saml';
+ const fakeSamlPath = '/example_group_saml';
beforeEach(async () => {
mr.requireSamlAuthToApprove = true;
- mr.samlApprovalPath = fakeGroupSamlPath;
+ mr.samlApprovalPath = fakeSamlPath;
createComponent({}, { query: createCanApproveResponse() });
await waitForPromises();
@@ -269,9 +278,10 @@ describe('MRWidget approvals', () => {
it('redirects the user to the group SAML path', async () => {
const action = findAction();
- action.vm.$emit('click');
- await nextTick();
- expect(visitUrl).toHaveBeenCalledWith(fakeGroupSamlPath);
+
+ await action.trigger('click');
+
+ expect(submitSpy).toHaveBeenCalled();
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/checks/draft_spec.js b/spec/frontend/vue_merge_request_widget/components/checks/draft_spec.js
new file mode 100644
index 00000000000..cc605c8c83d
--- /dev/null
+++ b/spec/frontend/vue_merge_request_widget/components/checks/draft_spec.js
@@ -0,0 +1,196 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+
+import getStateQueryResponse from 'test_fixtures/graphql/merge_requests/get_state.query.graphql.json';
+
+import { createAlert } from '~/alert';
+
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+
+import MergeRequest from '~/merge_request';
+
+import DraftCheck from '~/vue_merge_request_widget/components/checks/draft.vue';
+import {
+ DRAFT_CHECK_READY,
+ DRAFT_CHECK_ERROR,
+} from '~/vue_merge_request_widget/components/checks/i18n';
+import { FAILURE_REASONS } from '~/vue_merge_request_widget/components/checks/message.vue';
+
+import draftQuery from '~/vue_merge_request_widget/queries/states/draft.query.graphql';
+import getStateQuery from '~/vue_merge_request_widget/queries/get_state.query.graphql';
+import removeDraftMutation from '~/vue_merge_request_widget/queries/toggle_draft.mutation.graphql';
+
+Vue.use(VueApollo);
+
+const TEST_PROJECT_ID = getStateQueryResponse.data.project.id;
+const TEST_MR_ID = getStateQueryResponse.data.project.mergeRequest.id;
+const TEST_MR_IID = '23';
+const TEST_MR_TITLE = 'Test MR Title';
+const TEST_PROJECT_PATH = 'lorem/ipsum';
+
+jest.mock('~/alert');
+jest.mock('~/merge_request', () => ({ toggleDraftStatus: jest.fn() }));
+
+describe('~/vue_merge_request_widget/components/checks/draft.vue', () => {
+ let wrapper;
+ let apolloProvider;
+
+ let draftQuerySpy;
+ let removeDraftMutationSpy;
+
+ const findMarkReadyButton = () => wrapper.findByTestId('mark-as-ready-button');
+
+ const createDraftQueryResponse = (canUpdateMergeRequest) => ({
+ data: {
+ project: {
+ __typename: 'Project',
+ id: TEST_PROJECT_ID,
+ mergeRequest: {
+ __typename: 'MergeRequest',
+ id: TEST_MR_ID,
+ draft: true,
+ title: TEST_MR_TITLE,
+ mergeableDiscussionsState: false,
+ userPermissions: {
+ updateMergeRequest: canUpdateMergeRequest,
+ },
+ },
+ },
+ },
+ });
+ const createRemoveDraftMutationResponse = () => ({
+ data: {
+ mergeRequestSetDraft: {
+ __typename: 'MergeRequestSetWipPayload',
+ errors: [],
+ mergeRequest: {
+ __typename: 'MergeRequest',
+ id: TEST_MR_ID,
+ title: TEST_MR_TITLE,
+ draft: false,
+ mergeableDiscussionsState: true,
+ },
+ },
+ },
+ });
+
+ const createComponent = async () => {
+ wrapper = mountExtended(DraftCheck, {
+ apolloProvider,
+ propsData: {
+ mr: {
+ issuableId: TEST_MR_ID,
+ title: TEST_MR_TITLE,
+ iid: TEST_MR_IID,
+ targetProjectFullPath: TEST_PROJECT_PATH,
+ },
+ check: {
+ identifier: 'draft_status',
+ status: 'FAILED',
+ },
+ },
+ });
+
+ await waitForPromises();
+
+ // why: draft.vue has some coupling that this query has been read before
+ // for some reason this has to happen **after** the component has mounted
+ // or apollo throws errors.
+ apolloProvider.defaultClient.cache.writeQuery({
+ query: getStateQuery,
+ variables: {
+ projectPath: TEST_PROJECT_PATH,
+ iid: TEST_MR_IID,
+ },
+ data: getStateQueryResponse.data,
+ });
+ };
+
+ beforeEach(() => {
+ draftQuerySpy = jest.fn().mockResolvedValue(createDraftQueryResponse(true));
+ removeDraftMutationSpy = jest.fn().mockResolvedValue(createRemoveDraftMutationResponse());
+
+ apolloProvider = createMockApollo([
+ [draftQuery, draftQuerySpy],
+ [removeDraftMutation, removeDraftMutationSpy],
+ ]);
+ });
+
+ describe('when user can update MR', () => {
+ beforeEach(async () => {
+ await createComponent();
+ });
+
+ it('renders text', () => {
+ const message = wrapper.text();
+ expect(message).toContain(FAILURE_REASONS.draft_status);
+ });
+
+ it('renders mark ready button', () => {
+ expect(findMarkReadyButton().text()).toBe(DRAFT_CHECK_READY);
+ });
+
+ it('does not call remove draft mutation', () => {
+ expect(removeDraftMutationSpy).not.toHaveBeenCalled();
+ });
+
+ describe('when mark ready button is clicked', () => {
+ beforeEach(async () => {
+ findMarkReadyButton().vm.$emit('click');
+
+ await waitForPromises();
+ });
+
+ it('calls mutation spy', () => {
+ expect(removeDraftMutationSpy).toHaveBeenCalledWith({
+ draft: false,
+ iid: TEST_MR_IID,
+ projectPath: TEST_PROJECT_PATH,
+ });
+ });
+
+ it('does not create alert', () => {
+ expect(createAlert).not.toHaveBeenCalled();
+ });
+
+ it('calls toggleDraftStatus', () => {
+ expect(MergeRequest.toggleDraftStatus).toHaveBeenCalledWith(TEST_MR_TITLE, true);
+ });
+ });
+
+ describe('when mutation fails and ready button is clicked', () => {
+ beforeEach(async () => {
+ removeDraftMutationSpy.mockRejectedValue(new Error('TEST FAIL'));
+ findMarkReadyButton().vm.$emit('click');
+
+ await waitForPromises();
+ });
+
+ it('creates alert', () => {
+ expect(createAlert).toHaveBeenCalledWith({
+ message: DRAFT_CHECK_ERROR,
+ });
+ });
+
+ it('does not call toggleDraftStatus', () => {
+ expect(MergeRequest.toggleDraftStatus).not.toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('when user cannot update MR', () => {
+ beforeEach(async () => {
+ draftQuerySpy.mockResolvedValue(createDraftQueryResponse(false));
+
+ createComponent();
+
+ await waitForPromises();
+ });
+
+ it('does not render mark ready button', () => {
+ expect(findMarkReadyButton().exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/vue_merge_request_widget/components/checks/rebase_spec.js b/spec/frontend/vue_merge_request_widget/components/checks/rebase_spec.js
index d6c01aee3b1..d621999337d 100644
--- a/spec/frontend/vue_merge_request_widget/components/checks/rebase_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/checks/rebase_spec.js
@@ -28,7 +28,7 @@ const mockPipelineNodes = [
const mockQueryHandler = ({
rebaseInProgress = false,
targetBranch = '',
- pushToSourceBranch = false,
+ pushToSourceBranch = true,
nodes = mockPipelineNodes,
} = {}) =>
jest.fn().mockResolvedValue({
@@ -279,7 +279,7 @@ describe('Merge request merge checks rebase component', () => {
await waitForPromises();
- expect(findRebaseWithoutCiButton().exists()).toBe(true);
+ expect(findRebaseWithoutCiButton().exists()).toBe(false);
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/merge_checks_spec.js b/spec/frontend/vue_merge_request_widget/components/merge_checks_spec.js
index d39098b27c2..b19095cc686 100644
--- a/spec/frontend/vue_merge_request_widget/components/merge_checks_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/merge_checks_spec.js
@@ -138,7 +138,7 @@ describe('Merge request merge checks component', () => {
it.each`
identifier
${'conflict'}
- ${'unresolved_discussions'}
+ ${'discussions_not_resolved'}
${'need_rebase'}
${'default'}
`('renders $identifier merge check', async ({ identifier }) => {
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_expandable_section_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_expandable_section_spec.js
index 8eaed998eb5..5a5d29d3194 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_widget_expandable_section_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_expandable_section_spec.js
@@ -39,7 +39,7 @@ describe('MrWidgetExpanableSection', () => {
const collapse = findCollapse();
expect(collapse.exists()).toBe(true);
- expect(collapse.attributes('visible')).toBeUndefined();
+ expect(collapse.props('visible')).toBe(false);
});
});
@@ -60,7 +60,7 @@ describe('MrWidgetExpanableSection', () => {
const collapse = findCollapse();
expect(collapse.exists()).toBe(true);
- expect(collapse.attributes('visible')).toBe('true');
+ expect(collapse.props('visible')).toBe(true);
});
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js
index 35b4e222e01..3f0eb946194 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js
@@ -8,6 +8,7 @@ import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import MRWidgetPipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
import LegacyPipelineMiniGraph from '~/ci/pipeline_mini_graph/legacy_pipeline_mini_graph.vue';
import { SUCCESS } from '~/vue_merge_request_widget/constants';
+import { localeDateFormat } from '~/lib/utils/datetime/locale_dateformat';
import mockData from '../mock_data';
describe('MRWidgetPipeline', () => {
@@ -93,7 +94,7 @@ describe('MRWidgetPipeline', () => {
it('should render pipeline finished timestamp', () => {
expect(findPipelineFinishedAt().attributes()).toMatchObject({
- title: 'Apr 7, 2017 2:00pm UTC',
+ title: localeDateFormat.asDateTimeFull.format(mockData.pipeline.details.finished_at),
datetime: mockData.pipeline.details.finished_at,
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_status_icon_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_status_icon_spec.js
index b210327aa31..65c4970bc76 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_widget_status_icon_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_status_icon_spec.js
@@ -54,5 +54,12 @@ describe('MR widget status icon component', () => {
expect(findIcon().exists()).toBe(true);
expect(findIcon().props().name).toBe('merge-request-close');
});
+
+ it('renders empty status icon', () => {
+ createWrapper({ status: 'empty' });
+
+ expect(findStatusIcon().exists()).toBe(true);
+ expect(findStatusIcon().props().iconName).toBe('neutral');
+ });
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/new_ready_to_merge_spec.js.snap b/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/new_ready_to_merge_spec.js.snap
index ecf4040cbda..ec0af7c8a7b 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/new_ready_to_merge_spec.js.snap
+++ b/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/new_ready_to_merge_spec.js.snap
@@ -8,7 +8,7 @@ exports[`New ready to merge state component renders permission text if canMerge
status="success"
/>
<p
- class="gl-font-weight-bold gl-m-0! gl-text-gray-900! media-body"
+ class="gl-font-weight-bold gl-mb-0! gl-mt-1 gl-text-gray-900! media-body"
>
Ready to merge by members who can write to the target branch.
</p>
@@ -23,7 +23,7 @@ exports[`New ready to merge state component renders permission text if canMerge
status="success"
/>
<p
- class="gl-font-weight-bold gl-m-0! gl-text-gray-900! media-body"
+ class="gl-font-weight-bold gl-mb-0! gl-mt-1 gl-text-gray-900! media-body"
>
Ready to merge!
</p>
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_conflicts_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_conflicts_spec.js
index 7f0a171d712..af10d7d5eb7 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_conflicts_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_conflicts_spec.js
@@ -1,10 +1,17 @@
import { mount } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
+import Vue from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
import { removeBreakLine } from 'helpers/text_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import userPermissionsQuery from '~/vue_merge_request_widget/queries/permissions.query.graphql';
+import conflictsStateQuery from '~/vue_merge_request_widget/queries/states/conflicts.query.graphql';
import ConflictsComponent from '~/vue_merge_request_widget/components/states/mr_widget_conflicts.vue';
+Vue.use(VueApollo);
+
describe('MRWidgetConflicts', () => {
let wrapper;
const path = '/conflicts';
@@ -20,34 +27,57 @@ describe('MRWidgetConflicts', () => {
const resolveConflictsBtnText = 'Resolve conflicts';
const mergeLocallyBtnText = 'Resolve locally';
- async function createComponent(propsData = {}) {
- wrapper = extendedWrapper(
- mount(ConflictsComponent, {
- propsData,
- data() {
- return {
+ const defaultApolloProvider = (mockData = {}) => {
+ const userData = {
+ data: {
+ project: {
+ id: 234,
+ mergeRequest: {
+ id: 234,
userPermissions: {
- canMerge: propsData.mr.canMerge,
- pushToSourceBranch: propsData.mr.canPushToSourceBranch,
- },
- state: {
- shouldBeRebased: propsData.mr.shouldBeRebased,
- sourceBranchProtected: propsData.mr.sourceBranchProtected,
+ canMerge: mockData.canMerge || false,
+ pushToSourceBranch: mockData.canPushToSourceBranch || false,
},
- };
+ },
},
- mocks: {
- $apollo: {
- queries: {
- userPermissions: { loading: false },
- stateData: { loading: false },
+ },
+ };
+
+ const mrData = {
+ data: {
+ project: {
+ id: 234,
+ mergeRequest: {
+ id: 234,
+ shouldBeRebased: mockData.shouldBeRebased || false,
+ sourceBranchProtected: mockData.sourceBranchProtected || false,
+ userPermissions: {
+ pushToSourceBranch: mockData.canPushToSourceBranch || false,
},
},
},
+ },
+ };
+
+ return createMockApollo([
+ [userPermissionsQuery, jest.fn().mockResolvedValue(userData)],
+ [conflictsStateQuery, jest.fn().mockResolvedValue(mrData)],
+ ]);
+ };
+
+ async function createComponent({
+ propsData,
+ queryData,
+ apolloProvider = defaultApolloProvider(queryData),
+ } = {}) {
+ wrapper = extendedWrapper(
+ mount(ConflictsComponent, {
+ apolloProvider,
+ propsData,
}),
);
- await nextTick();
+ await waitForPromises();
}
// There are two permissions we need to consider:
@@ -62,11 +92,15 @@ describe('MRWidgetConflicts', () => {
describe('when allowed to merge but not allowed to push to source branch', () => {
beforeEach(async () => {
await createComponent({
- mr: {
+ propsData: {
+ mr: {
+ conflictsDocsPath: '',
+ },
+ },
+ queryData: {
canMerge: true,
canPushToSourceBranch: false,
conflictResolutionPath: path,
- conflictsDocsPath: '',
},
});
});
@@ -89,11 +123,15 @@ describe('MRWidgetConflicts', () => {
describe('when not allowed to merge but allowed to push to source branch', () => {
beforeEach(async () => {
await createComponent({
- mr: {
+ propsData: {
+ mr: {
+ conflictResolutionPath: path,
+ conflictsDocsPath: '',
+ },
+ },
+ queryData: {
canMerge: false,
canPushToSourceBranch: true,
- conflictResolutionPath: path,
- conflictsDocsPath: '',
},
});
});
@@ -116,11 +154,15 @@ describe('MRWidgetConflicts', () => {
describe('when allowed to merge and push to source branch', () => {
beforeEach(async () => {
await createComponent({
- mr: {
+ queryData: {
canMerge: true,
canPushToSourceBranch: true,
- conflictResolutionPath: path,
- conflictsDocsPath: '',
+ },
+ propsData: {
+ mr: {
+ conflictResolutionPath: path,
+ conflictsDocsPath: '',
+ },
},
});
});
@@ -144,10 +186,14 @@ describe('MRWidgetConflicts', () => {
describe('when user does not have permission to push to source branch', () => {
it('should show proper message', async () => {
await createComponent({
- mr: {
+ propsData: {
+ mr: {
+ conflictsDocsPath: '',
+ },
+ },
+ queryData: {
canMerge: false,
canPushToSourceBranch: false,
- conflictsDocsPath: '',
},
});
@@ -156,10 +202,14 @@ describe('MRWidgetConflicts', () => {
it('should not have action buttons', async () => {
await createComponent({
- mr: {
+ queryData: {
canMerge: false,
canPushToSourceBranch: false,
- conflictsDocsPath: '',
+ },
+ propsData: {
+ mr: {
+ conflictsDocsPath: '',
+ },
},
});
@@ -169,10 +219,14 @@ describe('MRWidgetConflicts', () => {
it('should not have resolve button when no conflict resolution path', async () => {
await createComponent({
- mr: {
+ propsData: {
+ mr: {
+ conflictResolutionPath: null,
+ conflictsDocsPath: '',
+ },
+ },
+ queryData: {
canMerge: true,
- conflictResolutionPath: null,
- conflictsDocsPath: '',
},
});
@@ -183,9 +237,13 @@ describe('MRWidgetConflicts', () => {
describe('when fast-forward or semi-linear merge enabled', () => {
it('should tell you to rebase locally', async () => {
await createComponent({
- mr: {
+ propsData: {
+ mr: {
+ conflictsDocsPath: '',
+ },
+ },
+ queryData: {
shouldBeRebased: true,
- conflictsDocsPath: '',
},
});
@@ -196,12 +254,16 @@ describe('MRWidgetConflicts', () => {
describe('when source branch protected', () => {
beforeEach(async () => {
await createComponent({
- mr: {
+ propsData: {
+ mr: {
+ conflictResolutionPath: TEST_HOST,
+ conflictsDocsPath: '',
+ },
+ },
+ queryData: {
canMerge: true,
- canPushToSourceBranch: true,
- conflictResolutionPath: TEST_HOST,
sourceBranchProtected: true,
- conflictsDocsPath: '',
+ canPushToSourceBranch: true,
},
});
});
@@ -214,12 +276,16 @@ describe('MRWidgetConflicts', () => {
describe('when source branch not protected', () => {
beforeEach(async () => {
await createComponent({
- mr: {
- canMerge: true,
+ propsData: {
+ mr: {
+ conflictResolutionPath: TEST_HOST,
+ conflictsDocsPath: '',
+ },
+ },
+ queryData: {
canPushToSourceBranch: true,
- conflictResolutionPath: TEST_HOST,
+ canMerge: true,
sourceBranchProtected: false,
- conflictsDocsPath: '',
},
});
});
@@ -229,4 +295,21 @@ describe('MRWidgetConflicts', () => {
expect(findResolveButton().attributes('href')).toEqual(TEST_HOST);
});
});
+
+ describe('error states', () => {
+ it('when project is null due to expired session it does not throw', async () => {
+ const fn = async () => {
+ await createComponent({
+ propsData: { mr: {} },
+ apolloProvider: createMockApollo([
+ [conflictsStateQuery, jest.fn().mockResolvedValue({ data: { project: null } })],
+ [userPermissionsQuery, jest.fn().mockResolvedValue({ data: { project: null } })],
+ ]),
+ });
+ await waitForPromises();
+ };
+
+ await expect(fn()).resolves.not.toThrow();
+ });
+ });
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_merging_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_merging_spec.js
index 85acd5f9a9e..328c0134368 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_merging_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_merging_spec.js
@@ -1,8 +1,12 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import simplePoll from '~/lib/utils/simple_poll';
import MrWidgetMerging from '~/vue_merge_request_widget/components/states/mr_widget_merging.vue';
import BoldText from '~/vue_merge_request_widget/components/bold_text.vue';
+import { STATUS_MERGED } from '~/issues/constants';
+import { fetchUserCounts } from '~/super_sidebar/user_counts_fetch';
+jest.mock('~/super_sidebar/user_counts_fetch');
jest.mock('~/lib/utils/simple_poll', () =>
jest.fn().mockImplementation(jest.requireActual('~/lib/utils/simple_poll').default),
);
@@ -13,7 +17,7 @@ describe('MRWidgetMerging', () => {
const pollMock = jest.fn().mockResolvedValue();
const GlEmoji = { template: '<img />' };
- beforeEach(() => {
+ const createComponent = () => {
wrapper = shallowMount(MrWidgetMerging, {
propsData: {
mr: {
@@ -29,14 +33,18 @@ describe('MRWidgetMerging', () => {
GlEmoji,
},
});
- });
+ };
it('renders information about merge request being merged', () => {
+ createComponent();
+
const message = wrapper.findComponent(BoldText).props('message');
expect(message).toContain('Merging!');
});
describe('initiateMergePolling', () => {
+ beforeEach(createComponent);
+
it('should call simplePoll', () => {
expect(simplePoll).toHaveBeenCalledWith(expect.any(Function), { timeout: 0 });
});
@@ -45,4 +53,15 @@ describe('MRWidgetMerging', () => {
expect(pollMock).toHaveBeenCalled();
});
});
+
+ describe('on successful merge', () => {
+ it('should re-fetch user counts', async () => {
+ pollMock.mockResolvedValueOnce({ data: { state: STATUS_MERGED } });
+ createComponent();
+
+ await nextTick();
+
+ expect(fetchUserCounts).toHaveBeenCalled();
+ });
+ });
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge_spec.js
index 016eac05727..d8eec165395 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge_spec.js
@@ -1,5 +1,6 @@
-import { GlSprintf } from '@gitlab/ui';
+import { GlSprintf, GlLink } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { helpPagePath } from '~/helpers/help_page_helper';
import NothingToMerge from '~/vue_merge_request_widget/components/states/nothing_to_merge.vue';
describe('NothingToMerge', () => {
@@ -14,6 +15,7 @@ describe('NothingToMerge', () => {
};
const findNothingToMergeTextBody = () => wrapper.findByTestId('nothing-to-merge-body');
+ const findHelpLink = () => wrapper.findComponent(GlLink);
describe('With Blob link', () => {
beforeEach(() => {
@@ -26,5 +28,9 @@ describe('NothingToMerge', () => {
'Use merge requests to propose changes to your project and discuss them with your team. To make changes, use the Code dropdown list above, then test them with CI/CD before merging.',
);
});
+
+ it('renders text with link to CI Help Page', () => {
+ expect(findHelpLink().attributes('href')).toBe(helpPagePath('ci/quick_start/index.html'));
+ });
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js
index 9239807ae71..1b7338744e8 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -1,9 +1,10 @@
-import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import { GlSprintf } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
import produce from 'immer';
+import { createMockSubscription as createMockApolloSubscription } from 'mock-apollo-client';
import readyToMergeResponse from 'test_fixtures/graphql/merge_requests/states/ready_to_merge.query.graphql.json';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import createMockApollo from 'helpers/mock_apollo_helper';
import readyToMergeQuery from 'ee_else_ce/vue_merge_request_widget/queries/states/ready_to_merge.query.graphql';
@@ -15,13 +16,11 @@ import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squa
import MergeFailedPipelineConfirmationDialog from '~/vue_merge_request_widget/components/states/merge_failed_pipeline_confirmation_dialog.vue';
import { MWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants';
import eventHub from '~/vue_merge_request_widget/event_hub';
+import readyToMergeSubscription from '~/vue_merge_request_widget/queries/states/ready_to_merge.subscription.graphql';
jest.mock('~/lib/utils/simple_poll', () =>
jest.fn().mockImplementation(jest.requireActual('~/lib/utils/simple_poll').default),
);
-jest.mock('~/commons/nav/user_merge_requests', () => ({
- refreshUserMergeRequestCounts: jest.fn(),
-}));
const commitMessage = readyToMergeResponse.data.project.mergeRequest.defaultMergeCommitMessage;
const squashCommitMessage =
@@ -82,6 +81,7 @@ Vue.use(VueApollo);
let service;
let wrapper;
let readyToMergeResponseSpy;
+let mockedSubscription;
const createReadyToMergeResponse = (customMr) => {
return produce(readyToMergeResponse, (draft) => {
@@ -90,7 +90,21 @@ const createReadyToMergeResponse = (customMr) => {
};
const createComponent = (customConfig = {}, createState = true) => {
- wrapper = shallowMount(ReadyToMerge, {
+ mockedSubscription = createMockApolloSubscription();
+ const apolloProvider = createMockApollo([[readyToMergeQuery, readyToMergeResponseSpy]]);
+ const subscriptionResponse = {
+ data: { mergeRequestMergeStatusUpdated: { ...readyToMergeResponse.data.project.mergeRequest } },
+ };
+ subscriptionResponse.data.mergeRequestMergeStatusUpdated.defaultMergeCommitMessage =
+ 'New default merge commit message';
+
+ const subscriptionHandlers = [[readyToMergeSubscription, () => mockedSubscription]];
+
+ subscriptionHandlers.forEach(([query, stream]) => {
+ apolloProvider.defaultClient.setRequestHandler(query, stream);
+ });
+
+ wrapper = shallowMountExtended(ReadyToMerge, {
propsData: {
mr: createTestMr(customConfig),
service,
@@ -112,7 +126,7 @@ const createComponent = (customConfig = {}, createState = true) => {
CommitEdit,
GlSprintf,
},
- apolloProvider: createMockApollo([[readyToMergeQuery, readyToMergeResponseSpy]]),
+ apolloProvider,
});
};
@@ -843,4 +857,60 @@ describe('ReadyToMerge', () => {
expect(wrapper.text()).not.toContain('Auto-merge enabled');
});
});
+
+ describe('commit message', () => {
+ it('updates commit message from subscription', async () => {
+ createComponent({ mr: { id: 1 } });
+
+ await waitForPromises();
+
+ await wrapper.findByTestId('widget_edit_commit_message').vm.$emit('input', true);
+
+ expect(wrapper.findByTestId('merge-commit-message').props('value')).not.toEqual(
+ 'Updated commit message',
+ );
+
+ mockedSubscription.next({
+ data: {
+ mergeRequestMergeStatusUpdated: {
+ ...readyToMergeResponse.data.project.mergeRequest,
+ defaultMergeCommitMessage: 'Updated commit message',
+ },
+ },
+ });
+
+ await waitForPromises();
+
+ expect(wrapper.findByTestId('merge-commit-message').props('value')).toEqual(
+ 'Updated commit message',
+ );
+ });
+
+ it('does not update commit message from subscription if commit message has been manually changed', async () => {
+ createComponent({ mr: { id: 1 } });
+
+ await waitForPromises();
+
+ await wrapper.findByTestId('widget_edit_commit_message').vm.$emit('input', true);
+
+ await wrapper
+ .findByTestId('merge-commit-message')
+ .vm.$emit('input', 'Manually updated commit message');
+
+ mockedSubscription.next({
+ data: {
+ mergeRequestMergeStatusUpdated: {
+ ...readyToMergeResponse.data.project.mergeRequest,
+ defaultMergeCommitMessage: 'Updated commit message',
+ },
+ },
+ });
+
+ await waitForPromises();
+
+ expect(wrapper.findByTestId('merge-commit-message').props('value')).toEqual(
+ 'Manually updated commit message',
+ );
+ });
+ });
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/work_in_progress_spec.js b/spec/frontend/vue_merge_request_widget/components/states/work_in_progress_spec.js
index f46829539a8..f01df2ca419 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/work_in_progress_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/work_in_progress_spec.js
@@ -42,6 +42,9 @@ describe('~/vue_merge_request_widget/components/states/work_in_progress.vue', ()
mergeRequest: {
__typename: 'MergeRequest',
id: TEST_MR_ID,
+ draft: true,
+ title: TEST_MR_TITLE,
+ mergeableDiscussionsState: false,
userPermissions: {
updateMergeRequest: canUpdateMergeRequest,
},
@@ -179,4 +182,17 @@ describe('~/vue_merge_request_widget/components/states/work_in_progress.vue', ()
expect(findWIPButton().exists()).toBe(false);
});
});
+
+ describe('when project is null', () => {
+ beforeEach(async () => {
+ draftQuerySpy.mockResolvedValue({ data: { project: null } });
+ createComponent();
+ await waitForPromises();
+ });
+
+ // This is to mitigate https://gitlab.com/gitlab-org/gitlab/-/issues/413627
+ it('does not throw any error', () => {
+ expect(wrapper.exists()).toBe(true);
+ });
+ });
});
diff --git a/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap b/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap
index d5d3f56e451..f2a66ad2ff2 100644
--- a/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap
+++ b/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap
@@ -49,7 +49,7 @@ exports[`~/vue_merge_request_widget/components/widget/dynamic_content.vue render
name="MyWidget"
/>
<div
- class="gl-display-flex gl-w-full"
+ class="gl-display-flex gl-flex-direction-column gl-w-full"
>
<div
class="gl-display-flex gl-flex-grow-1"
@@ -88,8 +88,7 @@ exports[`~/vue_merge_request_widget/components/widget/dynamic_content.vue render
>
<li>
<div
- class="gl-align-items-center gl-display-flex"
- data-qa-selector="child_content"
+ class="gl-align-items-baseline gl-display-flex"
>
<div
class="gl-min-w-0 gl-w-full"
@@ -111,7 +110,7 @@ exports[`~/vue_merge_request_widget/components/widget/dynamic_content.vue render
class="gl-align-items-baseline gl-display-flex"
>
<div
- class="gl-display-flex gl-w-full"
+ class="gl-display-flex gl-flex-direction-column gl-w-full"
>
<div
class="gl-display-flex gl-flex-grow-1"