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
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-03-07 12:08:26 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-03-07 12:08:26 +0300
commit47da68850624438b5b5b9ff9d648b1e83282c446 (patch)
tree95f31cd727324ad14cc64e5e5d8b41830cecefe8 /spec
parented922e61f47716d82396490b0a19d794c997b9c5 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/factories/notes.rb5
-rw-r--r--spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js8
-rw-r--r--spec/frontend/artifacts/components/artifacts_table_row_details_spec.js6
-rw-r--r--spec/frontend/artifacts/components/job_artifacts_table_spec.js4
-rw-r--r--spec/frontend/error_tracking/components/error_details_spec.js6
-rw-r--r--spec/frontend/incidents_settings/components/incidents_settings_service_spec.js6
-rw-r--r--spec/frontend/issues/show/components/header_actions_spec.js6
-rw-r--r--spec/frontend/releases/components/app_index_spec.js12
-rw-r--r--spec/frontend/releases/components/app_show_spec.js10
-rw-r--r--spec/frontend/releases/release_notification_service_spec.js8
-rw-r--r--spec/frontend/releases/stores/modules/detail/actions_spec.js20
-rw-r--r--spec/frontend/repository/commits_service_spec.js4
-rw-r--r--spec/frontend/repository/components/fork_info_spec.js4
-rw-r--r--spec/frontend/repository/components/new_directory_modal_spec.js6
-rw-r--r--spec/frontend/repository/components/tree_content_spec.js4
-rw-r--r--spec/frontend/repository/components/upload_blob_modal_spec.js6
-rw-r--r--spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js6
-rw-r--r--spec/frontend/sidebar/components/labels/labels_select_widget/labels_select_root_spec.js6
-rw-r--r--spec/frontend/sidebar/components/sidebar_dropdown_spec.js4
-rw-r--r--spec/frontend/sidebar/components/sidebar_dropdown_widget_spec.js4
-rw-r--r--spec/frontend/snippets/components/edit_spec.js10
-rw-r--r--spec/frontend/snippets/components/snippet_blob_edit_spec.js6
-rw-r--r--spec/frontend/snippets/components/snippet_header_spec.js6
-rw-r--r--spec/lib/gitlab/ci/parsers/security/common_spec.rb8
-rw-r--r--spec/models/note_spec.rb10
-rw-r--r--spec/models/work_item_spec.rb24
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/note_spec.rb16
-rw-r--r--spec/services/notes/quick_actions_service_spec.rb84
-rw-r--r--spec/support/shared_examples/graphql/notes_quick_actions_for_work_items_shared_examples.rb154
29 files changed, 375 insertions, 78 deletions
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index 530b4616765..2a21bde5436 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -12,6 +12,7 @@ FactoryBot.define do
factory :note_on_commit, traits: [:on_commit]
factory :note_on_issue, traits: [:on_issue], aliases: [:votable_note]
+ factory :note_on_work_item, traits: [:on_work_item]
factory :note_on_merge_request, traits: [:on_merge_request]
factory :note_on_project_snippet, traits: [:on_project_snippet]
factory :note_on_personal_snippet, traits: [:on_personal_snippet]
@@ -122,6 +123,10 @@ FactoryBot.define do
noteable { association(:issue, project: project) }
end
+ trait :on_work_item do
+ noteable { association(:work_item, project: project) }
+ end
+
trait :on_merge_request do
noteable { association(:merge_request, source_project: project) }
end
diff --git a/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js b/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js
index a15c78cc456..11a16ade9ed 100644
--- a/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js
+++ b/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js
@@ -30,7 +30,7 @@ import {
INTEGRATION_INACTIVE_PAYLOAD_TEST_ERROR,
DELETE_INTEGRATION_ERROR,
} from '~/alerts_settings/utils/error_messages';
-import { createAlert, VARIANT_SUCCESS } from '~/flash';
+import { createAlert, VARIANT_SUCCESS } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_FORBIDDEN, HTTP_STATUS_UNPROCESSABLE_ENTITY } from '~/lib/utils/http_status';
import {
@@ -48,7 +48,7 @@ import {
} from './mocks/apollo_mock';
import mockIntegrations from './mocks/integrations.json';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('AlertsSettingsWrapper', () => {
let wrapper;
@@ -478,7 +478,7 @@ describe('AlertsSettingsWrapper', () => {
expect(destroyIntegrationHandler).toHaveBeenCalled();
});
- it('displays flash if mutation had a recoverable error', async () => {
+ it('displays alert if mutation had a recoverable error', async () => {
createComponentWithApollo({
destroyHandler: jest.fn().mockResolvedValue(destroyIntegrationResponseWithErrors),
});
@@ -489,7 +489,7 @@ describe('AlertsSettingsWrapper', () => {
expect(createAlert).toHaveBeenCalledWith({ message: 'Houston, we have a problem' });
});
- it('displays flash if mutation had a non-recoverable error', async () => {
+ it('displays alert if mutation had a non-recoverable error', async () => {
createComponentWithApollo({
destroyHandler: jest.fn().mockRejectedValue('Error'),
});
diff --git a/spec/frontend/artifacts/components/artifacts_table_row_details_spec.js b/spec/frontend/artifacts/components/artifacts_table_row_details_spec.js
index 87bf1be51c6..eced6d3f3ba 100644
--- a/spec/frontend/artifacts/components/artifacts_table_row_details_spec.js
+++ b/spec/frontend/artifacts/components/artifacts_table_row_details_spec.js
@@ -10,9 +10,9 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import destroyArtifactMutation from '~/artifacts/graphql/mutations/destroy_artifact.mutation.graphql';
import { I18N_DESTROY_ERROR, I18N_MODAL_TITLE } from '~/artifacts/constants';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
-jest.mock('~/flash');
+jest.mock('~/alert');
const { artifacts } = getJobArtifactsResponse.data.project.jobs.nodes[0];
const refetchArtifacts = jest.fn();
@@ -88,7 +88,7 @@ describe('ArtifactsTableRowDetails component', () => {
});
});
- it('displays a flash message and refetches artifacts when the mutation fails', async () => {
+ it('displays an alert message and refetches artifacts when the mutation fails', async () => {
createComponent({
destroyArtifactMutation: jest.fn().mockRejectedValue(new Error('Error!')),
});
diff --git a/spec/frontend/artifacts/components/job_artifacts_table_spec.js b/spec/frontend/artifacts/components/job_artifacts_table_spec.js
index 9919ea5ad2a..790b082c10b 100644
--- a/spec/frontend/artifacts/components/job_artifacts_table_spec.js
+++ b/spec/frontend/artifacts/components/job_artifacts_table_spec.js
@@ -19,9 +19,9 @@ import {
INITIAL_CURRENT_PAGE,
} from '~/artifacts/constants';
import { totalArtifactsSizeForJob } from '~/artifacts/utils';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
-jest.mock('~/flash');
+jest.mock('~/alert');
Vue.use(VueApollo);
diff --git a/spec/frontend/error_tracking/components/error_details_spec.js b/spec/frontend/error_tracking/components/error_details_spec.js
index 9d6e46be8c4..3bfade12d27 100644
--- a/spec/frontend/error_tracking/components/error_details_spec.js
+++ b/spec/frontend/error_tracking/components/error_details_spec.js
@@ -18,11 +18,11 @@ import {
trackErrorDetailsViewsOptions,
trackErrorStatusUpdateOptions,
} from '~/error_tracking/utils';
-import { createAlert, VARIANT_WARNING } from '~/flash';
+import { createAlert, VARIANT_WARNING } from '~/alert';
import { __ } from '~/locale';
import Tracking from '~/tracking';
-jest.mock('~/flash');
+jest.mock('~/alert');
Vue.use(Vuex);
@@ -148,7 +148,7 @@ describe('ErrorDetails', () => {
expect(mocks.$apollo.queries.error.stopPolling).not.toHaveBeenCalled();
});
- it('when timeout is hit and no apollo result, stops loading and shows flash', async () => {
+ it('when timeout is hit and no apollo result, stops loading and shows alert', async () => {
Date.now.mockReturnValue(endTime + 1);
wrapper.vm.onNoApolloResult();
diff --git a/spec/frontend/incidents_settings/components/incidents_settings_service_spec.js b/spec/frontend/incidents_settings/components/incidents_settings_service_spec.js
index 1d1b285c1b6..c5c29b4bb19 100644
--- a/spec/frontend/incidents_settings/components/incidents_settings_service_spec.js
+++ b/spec/frontend/incidents_settings/components/incidents_settings_service_spec.js
@@ -1,12 +1,12 @@
import AxiosMockAdapter from 'axios-mock-adapter';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { ERROR_MSG } from '~/incidents_settings/constants';
import IncidentsSettingsService from '~/incidents_settings/incidents_settings_service';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/lib/utils/url_utility');
describe('IncidentsSettingsService', () => {
@@ -33,7 +33,7 @@ describe('IncidentsSettingsService', () => {
});
});
- it('should display a flash message on update error', () => {
+ it('should display an alert message on update error', () => {
mock.onPatch().reply(HTTP_STATUS_BAD_REQUEST);
return service.updateSettings({}).then(() => {
diff --git a/spec/frontend/issues/show/components/header_actions_spec.js b/spec/frontend/issues/show/components/header_actions_spec.js
index 54d8cceb523..00e6a6f8e90 100644
--- a/spec/frontend/issues/show/components/header_actions_spec.js
+++ b/spec/frontend/issues/show/components/header_actions_spec.js
@@ -4,7 +4,7 @@ import { shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import { mockTracking } from 'helpers/tracking_helper';
import { createAlert, VARIANT_SUCCESS } from '~/flash';
-import { IssueType, STATUS_CLOSED, STATUS_OPEN, TYPE_INCIDENT } from '~/issues/constants';
+import { STATUS_CLOSED, STATUS_OPEN, TYPE_INCIDENT, TYPE_ISSUE } from '~/issues/constants';
import DeleteIssueModal from '~/issues/show/components/delete_issue_modal.vue';
import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue';
import HeaderActions from '~/issues/show/components/header_actions.vue';
@@ -36,7 +36,7 @@ describe('HeaderActions component', () => {
iid: '32',
isIssueAuthor: true,
issuePath: 'gitlab-org/gitlab-test/-/issues/1',
- issueType: IssueType.Issue,
+ issueType: TYPE_ISSUE,
newIssuePath: 'gitlab-org/gitlab-test/-/issues/new',
projectPath: 'gitlab-org/gitlab-test',
reportAbusePath: '-/abuse_reports/add_category',
@@ -118,7 +118,7 @@ describe('HeaderActions component', () => {
describe.each`
issueType
- ${IssueType.Issue}
+ ${TYPE_ISSUE}
${TYPE_INCIDENT}
`('when issue type is $issueType', ({ issueType }) => {
describe('close/reopen button', () => {
diff --git a/spec/frontend/releases/components/app_index_spec.js b/spec/frontend/releases/components/app_index_spec.js
index ef3bd5ca873..7a0e9fb7326 100644
--- a/spec/frontend/releases/components/app_index_spec.js
+++ b/spec/frontend/releases/components/app_index_spec.js
@@ -6,7 +6,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import allReleasesQuery from '~/releases/graphql/queries/all_releases.query.graphql';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { historyPushState } from '~/lib/utils/common_utils';
import { sprintf, __ } from '~/locale';
import ReleasesIndexApp from '~/releases/components/app_index.vue';
@@ -20,7 +20,7 @@ import { deleteReleaseSessionKey } from '~/releases/util';
Vue.use(VueApollo);
-jest.mock('~/flash');
+jest.mock('~/alert');
let mockQueryParams;
jest.mock('~/lib/utils/common_utils', () => ({
@@ -114,7 +114,7 @@ describe('app_index.vue', () => {
const toDescription = (bool) => (bool ? 'does' : 'does not');
describe.each`
- description | singleResponseFn | fullResponseFn | loadingIndicator | emptyState | flashMessage | releaseCount | pagination
+ description | singleResponseFn | fullResponseFn | loadingIndicator | emptyState | alertMessage | releaseCount | pagination
${'both requests loading'} | ${getInProgressResponse} | ${getInProgressResponse} | ${true} | ${false} | ${false} | ${0} | ${false}
${'both requests failed'} | ${getErrorResponse} | ${getErrorResponse} | ${false} | ${false} | ${true} | ${0} | ${false}
${'both requests loaded'} | ${getSingleRequestLoadedResponse} | ${getFullRequestLoadedResponse} | ${false} | ${false} | ${false} | ${2} | ${true}
@@ -134,7 +134,7 @@ describe('app_index.vue', () => {
fullResponseFn,
loadingIndicator,
emptyState,
- flashMessage,
+ alertMessage,
releaseCount,
pagination,
}) => {
@@ -154,9 +154,9 @@ describe('app_index.vue', () => {
expect(findEmptyState().exists()).toBe(emptyState);
});
- it(`${toDescription(flashMessage)} show a flash message`, async () => {
+ it(`${toDescription(alertMessage)} show a flash message`, async () => {
await waitForPromises();
- if (flashMessage) {
+ if (alertMessage) {
expect(createAlert).toHaveBeenCalledWith({
message: ReleasesIndexApp.i18n.errorMessage,
captureError: true,
diff --git a/spec/frontend/releases/components/app_show_spec.js b/spec/frontend/releases/components/app_show_spec.js
index efe72e8000a..d8e9ff8c59d 100644
--- a/spec/frontend/releases/components/app_show_spec.js
+++ b/spec/frontend/releases/components/app_show_spec.js
@@ -4,14 +4,14 @@ import VueApollo from 'vue-apollo';
import oneReleaseQueryResponse from 'test_fixtures/graphql/releases/graphql/queries/one_release.query.graphql.json';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { popCreateReleaseNotification } from '~/releases/release_notification_service';
import ReleaseShowApp from '~/releases/components/app_show.vue';
import ReleaseBlock from '~/releases/components/release_block.vue';
import ReleaseSkeletonLoader from '~/releases/components/release_skeleton_loader.vue';
import oneReleaseQuery from '~/releases/graphql/queries/one_release.query.graphql';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/releases/release_notification_service');
Vue.use(VueApollo);
@@ -54,13 +54,13 @@ describe('Release show component', () => {
};
const expectNoFlash = () => {
- it('does not show a flash message', () => {
+ it('does not show an alert message', () => {
expect(createAlert).not.toHaveBeenCalled();
});
};
const expectFlashWithMessage = (message) => {
- it(`shows a flash message that reads "${message}"`, () => {
+ it(`shows an alert message that reads "${message}"`, () => {
expect(createAlert).toHaveBeenCalledWith({
message,
captureError: true,
@@ -152,7 +152,7 @@ describe('Release show component', () => {
beforeEach(async () => {
// As we return a release as `null`, Apollo also throws an error to the console
// about the missing field. We need to suppress console.error in order to check
- // that flash message was called
+ // that alert message was called
// eslint-disable-next-line no-console
console.error = jest.fn();
diff --git a/spec/frontend/releases/release_notification_service_spec.js b/spec/frontend/releases/release_notification_service_spec.js
index 2344d4b929a..a90bfa3dcbd 100644
--- a/spec/frontend/releases/release_notification_service_spec.js
+++ b/spec/frontend/releases/release_notification_service_spec.js
@@ -2,9 +2,9 @@ import {
popCreateReleaseNotification,
putCreateReleaseNotification,
} from '~/releases/release_notification_service';
-import { createAlert, VARIANT_SUCCESS } from '~/flash';
+import { createAlert, VARIANT_SUCCESS } from '~/alert';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('~/releases/release_notification_service', () => {
const projectPath = 'test-project-path';
@@ -35,7 +35,7 @@ describe('~/releases/release_notification_service', () => {
expect(item).toBe(null);
});
- it('should create a flash message', () => {
+ it('should create an alert message', () => {
expect(createAlert).toHaveBeenCalledTimes(1);
expect(createAlert).toHaveBeenCalledWith({
message: `Release ${releaseName} has been successfully created.`,
@@ -49,7 +49,7 @@ describe('~/releases/release_notification_service', () => {
popCreateReleaseNotification(projectPath);
});
- it('should not create a flash message', () => {
+ it('should not create an alert message', () => {
expect(createAlert).toHaveBeenCalledTimes(0);
});
});
diff --git a/spec/frontend/releases/stores/modules/detail/actions_spec.js b/spec/frontend/releases/stores/modules/detail/actions_spec.js
index ca3b2d5f734..2fca3396a1f 100644
--- a/spec/frontend/releases/stores/modules/detail/actions_spec.js
+++ b/spec/frontend/releases/stores/modules/detail/actions_spec.js
@@ -2,7 +2,7 @@ import { cloneDeep } from 'lodash';
import originalOneReleaseForEditingQueryResponse from 'test_fixtures/graphql/releases/graphql/queries/one_release_for_editing.query.graphql.json';
import testAction from 'helpers/vuex_action_helper';
import { getTag } from '~/api/tags_api';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { redirectTo } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
import { ASSET_LINK_TYPE } from '~/releases/constants';
@@ -21,7 +21,7 @@ import {
jest.mock('~/api/tags_api');
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/releases/release_notification_service');
@@ -154,7 +154,7 @@ describe('Release edit/new actions', () => {
]);
});
- it(`shows a flash message`, () => {
+ it(`shows an alert message`, () => {
return actions.fetchRelease({ commit: jest.fn(), state, rootState: state }).then(() => {
expect(createAlert).toHaveBeenCalledTimes(1);
expect(createAlert).toHaveBeenCalledWith({
@@ -380,7 +380,7 @@ describe('Release edit/new actions', () => {
]);
});
- it(`shows a flash message`, () => {
+ it(`shows an alert message`, () => {
return actions
.createRelease({ commit: jest.fn(), dispatch: jest.fn(), state, getters: {} })
.then(() => {
@@ -406,7 +406,7 @@ describe('Release edit/new actions', () => {
]);
});
- it(`shows a flash message`, () => {
+ it(`shows an alert message`, () => {
return actions
.createRelease({ commit: jest.fn(), dispatch: jest.fn(), state, getters: {} })
.then(() => {
@@ -538,7 +538,7 @@ describe('Release edit/new actions', () => {
expect(commit.mock.calls).toEqual([[types.RECEIVE_SAVE_RELEASE_ERROR, error]]);
});
- it('shows a flash message', async () => {
+ it('shows an alert message', async () => {
await actions.updateRelease({ commit, dispatch, state, getters });
expect(createAlert).toHaveBeenCalledTimes(1);
@@ -558,7 +558,7 @@ describe('Release edit/new actions', () => {
]);
});
- it('shows a flash message', async () => {
+ it('shows an alert message', async () => {
await actions.updateRelease({ commit, dispatch, state, getters });
expect(createAlert).toHaveBeenCalledTimes(1);
@@ -711,7 +711,7 @@ describe('Release edit/new actions', () => {
expect(commit.mock.calls).toContainEqual([types.RECEIVE_SAVE_RELEASE_ERROR, error]);
});
- it('shows a flash message', async () => {
+ it('shows an alert message', async () => {
await actions.deleteRelease({ commit, dispatch, state, getters });
expect(createAlert).toHaveBeenCalledTimes(1);
@@ -747,7 +747,7 @@ describe('Release edit/new actions', () => {
]);
});
- it('shows a flash message', async () => {
+ it('shows an alert message', async () => {
await actions.deleteRelease({ commit, dispatch, state, getters });
expect(createAlert).toHaveBeenCalledTimes(1);
@@ -778,7 +778,7 @@ describe('Release edit/new actions', () => {
expect(getTag).toHaveBeenCalledWith(state.projectId, tagName);
});
- it('creates a flash on error', async () => {
+ it('creates an alert on error', async () => {
error = new Error();
getTag.mockRejectedValue(error);
diff --git a/spec/frontend/repository/commits_service_spec.js b/spec/frontend/repository/commits_service_spec.js
index e56975d021a..22ef552c2f9 100644
--- a/spec/frontend/repository/commits_service_spec.js
+++ b/spec/frontend/repository/commits_service_spec.js
@@ -2,11 +2,11 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { loadCommits, isRequested, resetRequestedCommits } from '~/repository/commits_service';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { I18N_COMMIT_DATA_FETCH_ERROR } from '~/repository/constants';
import { refWithSpecialCharMock } from './mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('commits service', () => {
let mock;
diff --git a/spec/frontend/repository/components/fork_info_spec.js b/spec/frontend/repository/components/fork_info_spec.js
index f327a8cfae7..4b86d9425bc 100644
--- a/spec/frontend/repository/components/fork_info_spec.js
+++ b/spec/frontend/repository/components/fork_info_spec.js
@@ -4,13 +4,13 @@ import { GlSkeletonLoader, GlIcon, GlLink, GlSprintf } from '@gitlab/ui';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import ForkInfo, { i18n } from '~/repository/components/fork_info.vue';
import forkDetailsQuery from '~/repository/queries/fork_details.query.graphql';
import { propsForkInfo } from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('ForkInfo component', () => {
let wrapper;
diff --git a/spec/frontend/repository/components/new_directory_modal_spec.js b/spec/frontend/repository/components/new_directory_modal_spec.js
index 4e5c9a685c4..06e3dcbe9ea 100644
--- a/spec/frontend/repository/components/new_directory_modal_spec.js
+++ b/spec/frontend/repository/components/new_directory_modal_spec.js
@@ -4,12 +4,12 @@ import { nextTick } from 'vue';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { visitUrl } from '~/lib/utils/url_utility';
import NewDirectoryModal from '~/repository/components/new_directory_modal.vue';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/lib/utils/url_utility', () => ({
visitUrl: jest.fn(),
}));
@@ -188,7 +188,7 @@ describe('NewDirectoryModal', () => {
expect(findModal().props('actionPrimary').attributes[0].disabled).toBe(true);
});
- it('creates a flash error', async () => {
+ it('creates an alert error', async () => {
mock.onPost(initialProps.path).timeout();
await fillForm({ dirName: 'foo', branchName: 'master', commitMessage: 'foo' });
diff --git a/spec/frontend/repository/components/tree_content_spec.js b/spec/frontend/repository/components/tree_content_spec.js
index f694c8e9166..4f49fe2fa65 100644
--- a/spec/frontend/repository/components/tree_content_spec.js
+++ b/spec/frontend/repository/components/tree_content_spec.js
@@ -6,7 +6,7 @@ import FileTable from '~/repository/components/table/index.vue';
import TreeContent from 'jh_else_ce/repository/components/tree_content.vue';
import { loadCommits, isRequested, resetRequestedCommits } from '~/repository/commits_service';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { i18n } from '~/repository/constants';
import { graphQLErrors } from '../mock_data';
@@ -15,7 +15,7 @@ jest.mock('~/repository/commits_service', () => ({
isRequested: jest.fn(),
resetRequestedCommits: jest.fn(),
}));
-jest.mock('~/flash');
+jest.mock('~/alert');
let vm;
let $apollo;
diff --git a/spec/frontend/repository/components/upload_blob_modal_spec.js b/spec/frontend/repository/components/upload_blob_modal_spec.js
index 9de0666f27a..2397fe000cf 100644
--- a/spec/frontend/repository/components/upload_blob_modal_spec.js
+++ b/spec/frontend/repository/components/upload_blob_modal_spec.js
@@ -4,13 +4,13 @@ import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { visitUrl } from '~/lib/utils/url_utility';
import UploadBlobModal from '~/repository/components/upload_blob_modal.vue';
import UploadDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/lib/utils/url_utility', () => ({
visitUrl: jest.fn(),
joinPaths: () => '/new_upload',
@@ -184,7 +184,7 @@ describe('UploadBlobModal', () => {
await waitForPromises();
});
- it('creates a flash error', () => {
+ it('creates an alert error', () => {
expect(createAlert).toHaveBeenCalledWith({
message: 'Error uploading file. Please try again.',
});
diff --git a/spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js b/spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js
index 85cd8d51272..13670fac0ca 100644
--- a/spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js
+++ b/spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js
@@ -5,13 +5,13 @@ import { useFakeDate } from 'helpers/fake_date';
import { initEmojiMock, clearEmojiMock } from 'helpers/emoji';
import * as UserApi from '~/api/user_api';
import EmojiPicker from '~/emoji/components/picker.vue';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import stubChildren from 'helpers/stub_children';
import SetStatusModalWrapper from '~/set_status_modal/set_status_modal_wrapper.vue';
import { AVAILABILITY_STATUS } from '~/set_status_modal/constants';
import SetStatusForm from '~/set_status_modal/set_status_form.vue';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('SetStatusModalWrapper', () => {
let wrapper;
@@ -244,7 +244,7 @@ describe('SetStatusModalWrapper', () => {
return initModal({ mockOnUpdateFailure: false });
});
- it('flashes an error message', async () => {
+ it('alerts an error message', async () => {
findModal().vm.$emit('primary');
await nextTick();
diff --git a/spec/frontend/sidebar/components/labels/labels_select_widget/labels_select_root_spec.js b/spec/frontend/sidebar/components/labels/labels_select_widget/labels_select_root_spec.js
index c5432eea3ff..ae7c4068069 100644
--- a/spec/frontend/sidebar/components/labels/labels_select_widget/labels_select_root_spec.js
+++ b/spec/frontend/sidebar/components/labels/labels_select_widget/labels_select_root_spec.js
@@ -4,7 +4,7 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/flash';
-import { IssuableType, TYPE_EPIC, TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/issues/constants';
+import { TYPE_EPIC, TYPE_ISSUE, TYPE_MERGE_REQUEST, TYPE_TEST_CASE } from '~/issues/constants';
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
import DropdownContents from '~/sidebar/components/labels/labels_select_widget/dropdown_contents.vue';
import DropdownValue from '~/sidebar/components/labels/labels_select_widget/dropdown_value.vue';
@@ -38,7 +38,7 @@ const updateLabelsMutation = {
[TYPE_ISSUE]: updateIssueLabelsMutation,
[TYPE_MERGE_REQUEST]: updateMergeRequestLabelsMutation,
[TYPE_EPIC]: updateEpicLabelsMutation,
- [IssuableType.TestCase]: updateTestCaseLabelsMutation,
+ [TYPE_TEST_CASE]: updateTestCaseLabelsMutation,
};
describe('LabelsSelectRoot', () => {
@@ -216,7 +216,7 @@ describe('LabelsSelectRoot', () => {
${TYPE_ISSUE}
${TYPE_MERGE_REQUEST}
${TYPE_EPIC}
- ${IssuableType.TestCase}
+ ${TYPE_TEST_CASE}
`('when updating labels for $issuableType', ({ issuableType }) => {
const label = { id: 'gid://gitlab/ProjectLabel/2' };
diff --git a/spec/frontend/sidebar/components/sidebar_dropdown_spec.js b/spec/frontend/sidebar/components/sidebar_dropdown_spec.js
index 9f3d689edee..7a0044c00ac 100644
--- a/spec/frontend/sidebar/components/sidebar_dropdown_spec.js
+++ b/spec/frontend/sidebar/components/sidebar_dropdown_spec.js
@@ -10,7 +10,7 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { TYPE_ISSUE } from '~/issues/constants';
import SidebarDropdown from '~/sidebar/components/sidebar_dropdown.vue';
import { IssuableAttributeType } from '~/sidebar/constants';
@@ -23,7 +23,7 @@ import {
noCurrentMilestoneResponse,
} from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('SidebarDropdown component', () => {
let wrapper;
diff --git a/spec/frontend/sidebar/components/sidebar_dropdown_widget_spec.js b/spec/frontend/sidebar/components/sidebar_dropdown_widget_spec.js
index 0b7712cf29e..388a59eb6d2 100644
--- a/spec/frontend/sidebar/components/sidebar_dropdown_widget_spec.js
+++ b/spec/frontend/sidebar/components/sidebar_dropdown_widget_spec.js
@@ -7,7 +7,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { TYPE_ISSUE } from '~/issues/constants';
import { timeFor } from '~/lib/utils/datetime_utility';
@@ -27,7 +27,7 @@ import {
mockMilestone2,
} from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('SidebarDropdownWidget', () => {
let wrapper;
diff --git a/spec/frontend/snippets/components/edit_spec.js b/spec/frontend/snippets/components/edit_spec.js
index e7dab0ad79d..e24f96e22ad 100644
--- a/spec/frontend/snippets/components/edit_spec.js
+++ b/spec/frontend/snippets/components/edit_spec.js
@@ -9,7 +9,7 @@ import { stubPerformanceWebAPI } from 'helpers/performance';
import waitForPromises from 'helpers/wait_for_promises';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import GetSnippetQuery from 'shared_queries/snippet/snippet.query.graphql';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import * as urlUtils from '~/lib/utils/url_utility';
import SnippetEditApp from '~/snippets/components/edit.vue';
import SnippetBlobActionsEdit from '~/snippets/components/snippet_blob_actions_edit.vue';
@@ -25,7 +25,7 @@ import UpdateSnippetMutation from '~/snippets/mutations/update_snippet.mutation.
import FormFooterActions from '~/vue_shared/components/form/form_footer_actions.vue';
import { testEntries, createGQLSnippetsQueryResponse, createGQLSnippet } from '../test_utils';
-jest.mock('~/flash');
+jest.mock('~/alert');
const TEST_UPLOADED_FILES = ['foo/bar.txt', 'alpha/beta.js'];
const TEST_API_ERROR = new Error('TEST_API_ERROR');
@@ -347,7 +347,7 @@ describe('Snippet Edit app', () => {
projectPath
${'project/path'}
${''}
- `('should flash error (projectPath=$projectPath)', async ({ projectPath }) => {
+ `('should alert error (projectPath=$projectPath)', async ({ projectPath }) => {
mutateSpy.mockResolvedValue(createMutationResponseWithErrors('createSnippet'));
await createComponentAndLoad({
@@ -373,7 +373,7 @@ describe('Snippet Edit app', () => {
${'project/path'}
${''}
`(
- 'should flash error with (snippet=$snippetGid, projectPath=$projectPath)',
+ 'should alert error with (snippet=$snippetGid, projectPath=$projectPath)',
async ({ projectPath }) => {
mutateSpy.mockResolvedValue(createMutationResponseWithErrors('updateSnippet'));
@@ -405,7 +405,7 @@ describe('Snippet Edit app', () => {
expect(urlUtils.redirectTo).not.toHaveBeenCalled();
});
- it('should flash', () => {
+ it('should alert', () => {
// Apollo automatically wraps the resolver's error in a NetworkError
expect(createAlert).toHaveBeenCalledWith({
message: `Can't update snippet: ${TEST_API_ERROR.message}`,
diff --git a/spec/frontend/snippets/components/snippet_blob_edit_spec.js b/spec/frontend/snippets/components/snippet_blob_edit_spec.js
index 82c4a37ccc9..ec8e6517058 100644
--- a/spec/frontend/snippets/components/snippet_blob_edit_spec.js
+++ b/spec/frontend/snippets/components/snippet_blob_edit_spec.js
@@ -4,14 +4,14 @@ import AxiosMockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'helpers/test_constants';
import waitForPromises from 'helpers/wait_for_promises';
import BlobHeaderEdit from '~/blob/components/blob_edit_header.vue';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { joinPaths } from '~/lib/utils/url_utility';
import SnippetBlobEdit from '~/snippets/components/snippet_blob_edit.vue';
import SourceEditor from '~/vue_shared/components/source_editor.vue';
-jest.mock('~/flash');
+jest.mock('~/alert');
const TEST_ID = 'blob_local_7';
const TEST_PATH = 'foo/bar/test.md';
@@ -123,7 +123,7 @@ describe('Snippet Blob Edit component', () => {
createComponent();
});
- it('should call flash', async () => {
+ it('should call alert', async () => {
await waitForPromises();
expect(createAlert).toHaveBeenCalledWith({
diff --git a/spec/frontend/snippets/components/snippet_header_spec.js b/spec/frontend/snippets/components/snippet_header_spec.js
index c930c9f635b..21597d47206 100644
--- a/spec/frontend/snippets/components/snippet_header_spec.js
+++ b/spec/frontend/snippets/components/snippet_header_spec.js
@@ -10,9 +10,9 @@ import { differenceInMilliseconds } from '~/lib/utils/datetime_utility';
import SnippetHeader, { i18n } from '~/snippets/components/snippet_header.vue';
import DeleteSnippetMutation from '~/snippets/mutations/delete_snippet.mutation.graphql';
import axios from '~/lib/utils/axios_utils';
-import { createAlert, VARIANT_DANGER, VARIANT_SUCCESS } from '~/flash';
+import { createAlert, VARIANT_DANGER, VARIANT_SUCCESS } from '~/alert';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Snippet header component', () => {
let wrapper;
@@ -271,7 +271,7 @@ describe('Snippet header component', () => {
${200} | ${VARIANT_SUCCESS} | ${i18n.snippetSpamSuccess}
${500} | ${VARIANT_DANGER} | ${i18n.snippetSpamFailure}
`(
- 'renders a "$variant" flash message with "$text" message for a request with a "$request" response',
+ 'renders a "$variant" alert message with "$text" message for a request with a "$request" response',
async ({ request, variant, text }) => {
const submitAsSpamBtn = findButtons().at(2);
mock.onPost(reportAbusePath).reply(request);
diff --git a/spec/lib/gitlab/ci/parsers/security/common_spec.rb b/spec/lib/gitlab/ci/parsers/security/common_spec.rb
index 5d2d22c04fc..421aa29f860 100644
--- a/spec/lib/gitlab/ci/parsers/security/common_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/security/common_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Parsers::Security::Common do
+RSpec.describe Gitlab::Ci::Parsers::Security::Common, feature_category: :vulnerability_management do
describe '#parse!' do
let_it_be(:scanner_data) do
{
@@ -410,6 +410,12 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
end
end
+ describe 'setting the `found_by_pipeline` attribute' do
+ subject { report.findings.map(&:found_by_pipeline).uniq }
+
+ it { is_expected.to eq([pipeline]) }
+ end
+
describe 'parsing tracking' do
let(:finding) { report.findings.first }
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 013070f7be5..c1de8125c0d 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -1100,6 +1100,16 @@ RSpec.describe Note do
end
end
+ describe '#for_work_item?' do
+ it 'returns true for a work item' do
+ expect(build(:note_on_work_item).for_work_item?).to be true
+ end
+
+ it 'returns false for an issue' do
+ expect(build(:note_on_issue).for_work_item?).to be false
+ end
+ end
+
describe '#for_project_snippet?' do
it 'returns true for a project snippet note' do
expect(build(:note_on_project_snippet).for_project_snippet?).to be true
diff --git a/spec/models/work_item_spec.rb b/spec/models/work_item_spec.rb
index 6aacaa3c119..13f17e11276 100644
--- a/spec/models/work_item_spec.rb
+++ b/spec/models/work_item_spec.rb
@@ -163,6 +163,30 @@ RSpec.describe WorkItem, feature_category: :portfolio_management do
end
end
+ describe 'transform_quick_action_params' do
+ let(:work_item) { build(:work_item, :task) }
+
+ subject(:transformed_params) do
+ work_item.transform_quick_action_params({
+ title: 'bar',
+ assignee_ids: ['foo']
+ })
+ end
+
+ it 'correctly separates widget params from regular params' do
+ expect(transformed_params).to eq({
+ common: {
+ title: 'bar'
+ },
+ widgets: {
+ assignees_widget: {
+ assignee_ids: ['foo']
+ }
+ }
+ })
+ end
+ end
+
describe 'callbacks' do
describe 'record_create_action' do
it 'records the creation action after saving' do
diff --git a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
index a6253ba424b..2a0b5f291dc 100644
--- a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
@@ -104,7 +104,8 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do
end
context 'as work item' do
- let(:noteable) { create(:work_item, :issue, project: project) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:noteable) { create(:work_item, :issue, project: project) }
context 'when using internal param' do
let(:variables_extra) { { internal: true } }
@@ -130,6 +131,19 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do
it_behaves_like 'a mutation that returns top-level errors',
errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
end
+
+ context 'when body contains quick actions' do
+ let_it_be(:noteable) { create(:work_item, :task, project: project) }
+
+ let(:variables_extra) { {} }
+
+ it_behaves_like 'work item supports labels widget updates via quick actions'
+ it_behaves_like 'work item does not support labels widget updates via quick actions'
+ it_behaves_like 'work item supports assignee widget updates via quick actions'
+ it_behaves_like 'work item does not support assignee widget updates via quick actions'
+ it_behaves_like 'work item supports start and due date widget updates via quick actions'
+ it_behaves_like 'work item does not support start and due date widget updates via quick actions'
+ end
end
end
diff --git a/spec/services/notes/quick_actions_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb
index bca954c3959..0791b5855ec 100644
--- a/spec/services/notes/quick_actions_service_spec.rb
+++ b/spec/services/notes/quick_actions_service_spec.rb
@@ -253,6 +253,12 @@ RSpec.describe Notes::QuickActionsService do
describe '.noteable_update_service_class' do
include_context 'note on noteable'
+ it 'returns WorkItems::UpdateService for a note on a work item' do
+ note = create(:note_on_work_item, project: project)
+
+ expect(described_class.noteable_update_service_class(note)).to eq(WorkItems::UpdateService)
+ end
+
it 'returns Issues::UpdateService for a note on an issue' do
note = create(:note_on_issue, project: project)
@@ -322,6 +328,84 @@ RSpec.describe Notes::QuickActionsService do
let(:merge_request) { create(:merge_request, source_project: project) }
let(:note) { build(:note_on_merge_request, project: project, noteable: merge_request) }
end
+
+ context 'note on work item that supports quick actions' do
+ include_context 'note on noteable'
+
+ let_it_be(:work_item, reload: true) { create(:work_item, project: project) }
+
+ let(:note) { build(:note_on_work_item, project: project, noteable: work_item) }
+
+ let!(:labels) { create_pair(:label, project: project) }
+
+ before do
+ note.note = note_text
+ end
+
+ describe 'note with only command' do
+ describe '/close, /label & /assign' do
+ let(:note_text) do
+ %(/close\n/label ~#{labels.first.name} ~#{labels.last.name}\n/assign @#{assignee.username}\n)
+ end
+
+ it 'closes noteable, sets labels, assigns and leave no note' do
+ content = execute(note)
+
+ expect(content).to be_empty
+ expect(note.noteable).to be_closed
+ expect(note.noteable.labels).to match_array(labels)
+ expect(note.noteable.assignees).to eq([assignee])
+ end
+ end
+
+ describe '/reopen' do
+ before do
+ note.noteable.close!
+ expect(note.noteable).to be_closed
+ end
+ let(:note_text) { '/reopen' }
+
+ it 'opens the noteable, and leave no note' do
+ content = execute(note)
+
+ expect(content).to be_empty
+ expect(note.noteable).to be_open
+ end
+ end
+ end
+
+ describe 'note with command & text' do
+ describe '/close, /label, /assign' do
+ let(:note_text) do
+ %(HELLO\n/close\n/label ~#{labels.first.name} ~#{labels.last.name}\n/assign @#{assignee.username}\nWORLD)
+ end
+
+ it 'closes noteable, sets labels, assigns, and sets milestone to noteable' do
+ content = execute(note)
+
+ expect(content).to eq "HELLO\nWORLD"
+ expect(note.noteable).to be_closed
+ expect(note.noteable.labels).to match_array(labels)
+ expect(note.noteable.assignees).to eq([assignee])
+ end
+ end
+
+ describe '/reopen' do
+ before do
+ note.noteable.close
+ expect(note.noteable).to be_closed
+ end
+ let(:note_text) { "HELLO\n/reopen\nWORLD" }
+
+ it 'opens the noteable' do
+ content = execute(note)
+
+ expect(content).to eq "HELLO\nWORLD"
+ expect(note.noteable).to be_open
+ end
+ end
+ end
+ end
end
context 'CE restriction for issue assignees' do
diff --git a/spec/support/shared_examples/graphql/notes_quick_actions_for_work_items_shared_examples.rb b/spec/support/shared_examples/graphql/notes_quick_actions_for_work_items_shared_examples.rb
new file mode 100644
index 00000000000..5f4e7e5d4e7
--- /dev/null
+++ b/spec/support/shared_examples/graphql/notes_quick_actions_for_work_items_shared_examples.rb
@@ -0,0 +1,154 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'work item supports assignee widget updates via quick actions' do
+ let_it_be(:developer) { create(:user).tap { |user| project.add_developer(user) } }
+
+ context 'when assigning a user' do
+ let(:body) { "/assign @#{developer.username}" }
+
+ it 'updates the work item assignee' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ noteable.reload
+ end.to change { noteable.assignee_ids }.from([]).to([developer.id])
+
+ expect(response).to have_gitlab_http_status(:success)
+ end
+ end
+
+ context 'when unassigning a user' do
+ let(:body) { "/unassign @#{developer.username}" }
+
+ before do
+ noteable.update!(assignee_ids: [developer.id])
+ end
+
+ it 'updates the work item assignee' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ noteable.reload
+ end.to change { noteable.assignee_ids }.from([developer.id]).to([])
+
+ expect(response).to have_gitlab_http_status(:success)
+ end
+ end
+end
+
+RSpec.shared_examples 'work item does not support assignee widget updates via quick actions' do
+ let(:developer) { create(:user).tap { |user| project.add_developer(user) } }
+ let(:body) { "Updating assignee.\n/assign @#{developer.username}" }
+
+ before do
+ WorkItems::Type.default_by_type(:task).widget_definitions
+ .find_by_widget_type(:assignees).update!(disabled: true)
+ end
+
+ it 'ignores the quick action' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ noteable.reload
+ end.not_to change { noteable.assignee_ids }
+ end
+end
+
+RSpec.shared_examples 'work item supports labels widget updates via quick actions' do
+ shared_examples 'work item labels are updated' do
+ it do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ noteable.reload
+ end.to change { noteable.labels.count }.to(expected_labels.count)
+
+ expect(noteable.labels).to match_array(expected_labels)
+ end
+ end
+
+ let_it_be(:existing_label) { create(:label, project: project) }
+ let_it_be(:label1) { create(:label, project: project) }
+ let_it_be(:label2) { create(:label, project: project) }
+
+ let(:add_label_ids) { [] }
+ let(:remove_label_ids) { [] }
+
+ before_all do
+ noteable.update!(labels: [existing_label])
+ end
+
+ context 'when only removing labels' do
+ let(:remove_label_ids) { [existing_label.to_gid.to_s] }
+ let(:expected_labels) { [] }
+ let(:body) { "/remove_label ~\"#{existing_label.name}\"" }
+
+ it_behaves_like 'work item labels are updated'
+ end
+
+ context 'when only adding labels' do
+ let(:add_label_ids) { [label1.to_gid.to_s, label2.to_gid.to_s] }
+ let(:expected_labels) { [label1, label2, existing_label] }
+ let(:body) { "/labels ~\"#{label1.name}\" ~\"#{label2.name}\"" }
+
+ it_behaves_like 'work item labels are updated'
+ end
+
+ context 'when adding and removing labels' do
+ let(:remove_label_ids) { [existing_label.to_gid.to_s] }
+ let(:add_label_ids) { [label1.to_gid.to_s, label2.to_gid.to_s] }
+ let(:expected_labels) { [label1, label2] }
+ let(:body) { "/label ~\"#{label1.name}\" ~\"#{label2.name}\"\n/remove_label ~\"#{existing_label.name}\"" }
+
+ it_behaves_like 'work item labels are updated'
+ end
+end
+
+RSpec.shared_examples 'work item does not support labels widget updates via quick actions' do
+ let(:label1) { create(:label, project: project) }
+ let(:body) { "Updating labels.\n/labels ~\"#{label1.name}\"" }
+
+ before do
+ WorkItems::Type.default_by_type(:task).widget_definitions
+ .find_by_widget_type(:labels).update!(disabled: true)
+ end
+
+ it 'ignores the quick action' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ noteable.reload
+ end.not_to change { noteable.labels.count }
+
+ expect(noteable.labels).to be_empty
+ end
+end
+
+RSpec.shared_examples 'work item supports start and due date widget updates via quick actions' do
+ let(:due_date) { Date.today }
+ let(:body) { "/remove_due_date" }
+
+ before do
+ noteable.update!(due_date: due_date)
+ end
+
+ it 'updates start and due date' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ noteable.reload
+ end.to not_change(noteable, :start_date).and(
+ change { noteable.due_date }.from(due_date).to(nil)
+ )
+ end
+end
+
+RSpec.shared_examples 'work item does not support start and due date widget updates via quick actions' do
+ let(:body) { "Updating due date.\n/due today" }
+
+ before do
+ WorkItems::Type.default_by_type(:task).widget_definitions
+ .find_by_widget_type(:start_and_due_date).update!(disabled: true)
+ end
+
+ it 'ignores the quick action' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ noteable.reload
+ end.not_to change { noteable.due_date }
+ end
+end