summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-07-21 15:09:12 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-07-21 15:09:12 +0000
commita09c6d7e91de9abab7e2ea8dffce1cbb89bf95d8 (patch)
treebab46ee809784804b92228a0b860f3837b050755 /spec
parent32ac796097b0b8fbdbc22157307d1849a3e7b35c (diff)
downloadgitlab-ce-a09c6d7e91de9abab7e2ea8dffce1cbb89bf95d8.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/admin/broadcast_messages_controller_spec.rb20
-rw-r--r--spec/features/profiles/personal_access_tokens_spec.rb6
-rw-r--r--spec/frontend/access_tokens/components/projects_field_spec.js131
-rw-r--r--spec/frontend/access_tokens/components/projects_token_selector_spec.js266
-rw-r--r--spec/frontend/access_tokens/index_spec.js45
-rw-r--r--spec/lib/gitlab/error_tracking/error_repository/open_api_strategy_spec.rb2
-rw-r--r--spec/requests/api/internal/error_tracking_spec.rb2
-rw-r--r--spec/services/issues/clone_service_spec.rb4
-rw-r--r--spec/services/work_items/update_service_spec.rb37
-rw-r--r--spec/services/work_items/widgets/hierarchy_service/update_service_spec.rb8
10 files changed, 78 insertions, 443 deletions
diff --git a/spec/controllers/admin/broadcast_messages_controller_spec.rb b/spec/controllers/admin/broadcast_messages_controller_spec.rb
new file mode 100644
index 00000000000..66a54dc10ac
--- /dev/null
+++ b/spec/controllers/admin/broadcast_messages_controller_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Admin::BroadcastMessagesController do
+ before do
+ sign_in(create(:admin))
+ end
+
+ describe 'GET /preview' do
+ render_views
+
+ it 'renders preview partial' do
+ get :preview, params: { broadcast_message: { message: "Hello, world!" } }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body).to render_template(:_preview)
+ end
+ end
+end
diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb
index bca1bc4df4d..088c8a7a15a 100644
--- a/spec/features/profiles/personal_access_tokens_spec.rb
+++ b/spec/features/profiles/personal_access_tokens_spec.rb
@@ -146,12 +146,6 @@ RSpec.describe 'Profile > Personal Access Tokens', :js do
end
end
- it 'pushes `personal_access_tokens_scoped_to_projects` feature flag to the frontend' do
- visit profile_personal_access_tokens_path
-
- expect(page).to have_pushed_frontend_feature_flags(personalAccessTokensScopedToProjects: true)
- end
-
it "prefills token details" do
name = 'My PAT'
scopes = 'api,read_user'
diff --git a/spec/frontend/access_tokens/components/projects_field_spec.js b/spec/frontend/access_tokens/components/projects_field_spec.js
deleted file mode 100644
index 1c4fe7bb168..00000000000
--- a/spec/frontend/access_tokens/components/projects_field_spec.js
+++ /dev/null
@@ -1,131 +0,0 @@
-import { nextTick } from 'vue';
-import { within, fireEvent } from '@testing-library/dom';
-import { mount } from '@vue/test-utils';
-import ProjectsField from '~/access_tokens/components/projects_field.vue';
-import ProjectsTokenSelector from '~/access_tokens/components/projects_token_selector.vue';
-
-describe('ProjectsField', () => {
- let wrapper;
-
- const createComponent = ({ inputAttrsValue = '' } = {}) => {
- wrapper = mount(ProjectsField, {
- propsData: {
- inputAttrs: {
- id: 'projects',
- name: 'projects',
- value: inputAttrsValue,
- },
- },
- });
- };
-
- const queryByLabelText = (text) => within(wrapper.element).queryByLabelText(text);
- const queryByText = (text) => within(wrapper.element).queryByText(text);
- const findAllProjectsRadio = () => queryByLabelText('All projects');
- const findSelectedProjectsRadio = () => queryByLabelText('Selected projects');
- const findProjectsTokenSelector = () => wrapper.findComponent(ProjectsTokenSelector);
- const findHiddenInput = () => wrapper.find('input[type="hidden"]');
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- it('renders label and sub-label', () => {
- createComponent();
-
- expect(queryByText('Projects')).not.toBe(null);
- expect(queryByText('Set access permissions for this token.')).not.toBe(null);
- });
-
- describe('when `inputAttrs.value` is empty', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('renders "All projects" radio as checked', () => {
- expect(findAllProjectsRadio().checked).toBe(true);
- });
-
- it('renders "Selected projects" radio as unchecked', () => {
- expect(findSelectedProjectsRadio().checked).toBe(false);
- });
-
- it('sets `projects-token-selector` `initialProjectIds` prop to an empty array', () => {
- expect(findProjectsTokenSelector().props('initialProjectIds')).toEqual([]);
- });
- });
-
- describe('when `inputAttrs.value` is a comma separated list of project IDs', () => {
- beforeEach(() => {
- createComponent({ inputAttrsValue: '1,2' });
- });
-
- it('renders "All projects" radio as unchecked', () => {
- expect(findAllProjectsRadio().checked).toBe(false);
- });
-
- it('renders "Selected projects" radio as checked', () => {
- expect(findSelectedProjectsRadio().checked).toBe(true);
- });
-
- it('sets `projects-token-selector` `initialProjectIds` prop to an array of project IDs', () => {
- expect(findProjectsTokenSelector().props('initialProjectIds')).toEqual(['1', '2']);
- });
- });
-
- it('renders `projects-token-selector` component', () => {
- createComponent();
-
- expect(findProjectsTokenSelector().exists()).toBe(true);
- });
-
- it('renders hidden input with correct `name` and `id` attributes', () => {
- createComponent();
-
- expect(findHiddenInput().attributes()).toEqual(
- expect.objectContaining({
- id: 'projects',
- name: 'projects',
- }),
- );
- });
-
- describe('when `projects-token-selector` is focused', () => {
- beforeEach(() => {
- createComponent();
-
- findProjectsTokenSelector().vm.$emit('focus');
- });
-
- it('auto selects the "Selected projects" radio', () => {
- expect(findSelectedProjectsRadio().checked).toBe(true);
- });
-
- describe('when `projects-token-selector` is changed', () => {
- beforeEach(() => {
- findProjectsTokenSelector().vm.$emit('input', [
- {
- id: 1,
- },
- {
- id: 2,
- },
- ]);
- });
-
- it('updates the hidden input value to a comma separated list of project IDs', () => {
- expect(findHiddenInput().attributes('value')).toBe('1,2');
- });
-
- describe('when radio is changed back to "All projects"', () => {
- it('removes the hidden input value', async () => {
- fireEvent.change(findAllProjectsRadio());
- await nextTick();
-
- expect(findHiddenInput().attributes('value')).toBe('');
- });
- });
- });
- });
-});
diff --git a/spec/frontend/access_tokens/components/projects_token_selector_spec.js b/spec/frontend/access_tokens/components/projects_token_selector_spec.js
deleted file mode 100644
index 40aaf16d41f..00000000000
--- a/spec/frontend/access_tokens/components/projects_token_selector_spec.js
+++ /dev/null
@@ -1,266 +0,0 @@
-import {
- GlAvatar,
- GlAvatarLabeled,
- GlIntersectionObserver,
- GlToken,
- GlTokenSelector,
- GlLoadingIcon,
-} from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
-import produce from 'immer';
-import Vue from 'vue';
-import VueApollo from 'vue-apollo';
-
-import getProjectsQueryResponse from 'test_fixtures/graphql/projects/access_tokens/get_projects.query.graphql.json';
-import createMockApollo from 'helpers/mock_apollo_helper';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import waitForPromises from 'helpers/wait_for_promises';
-import ProjectsTokenSelector from '~/access_tokens/components/projects_token_selector.vue';
-import getProjectsQuery from '~/access_tokens/graphql/queries/get_projects.query.graphql';
-import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-
-describe('ProjectsTokenSelector', () => {
- const getProjectsQueryResponsePage2 = produce(
- getProjectsQueryResponse,
- (getProjectsQueryResponseDraft) => {
- /* eslint-disable no-param-reassign */
- getProjectsQueryResponseDraft.data.projects.pageInfo.hasNextPage = false;
- getProjectsQueryResponseDraft.data.projects.pageInfo.endCursor = null;
- getProjectsQueryResponseDraft.data.projects.nodes.splice(1, 1);
- getProjectsQueryResponseDraft.data.projects.nodes[0].id = 'gid://gitlab/Project/100';
- /* eslint-enable no-param-reassign */
- },
- );
-
- const runDebounce = () => jest.runAllTimers();
-
- const { pageInfo, nodes: projects } = getProjectsQueryResponse.data.projects;
- const project1 = projects[0];
- const project2 = projects[1];
-
- let wrapper;
-
- let resolveGetProjectsQuery;
- let resolveGetInitialProjectsQuery;
- const getProjectsQueryRequestHandler = jest.fn(
- ({ ids }) =>
- new Promise((resolve) => {
- if (ids) {
- resolveGetInitialProjectsQuery = resolve;
- } else {
- resolveGetProjectsQuery = resolve;
- }
- }),
- );
-
- const createComponent = ({
- propsData = {},
- apolloProvider = createMockApollo([[getProjectsQuery, getProjectsQueryRequestHandler]]),
- resolveQueries = true,
- } = {}) => {
- Vue.use(VueApollo);
-
- wrapper = extendedWrapper(
- mount(ProjectsTokenSelector, {
- apolloProvider,
- propsData: {
- selectedProjects: [],
- initialProjectIds: [],
- ...propsData,
- },
- stubs: ['gl-intersection-observer'],
- }),
- );
-
- runDebounce();
-
- if (resolveQueries) {
- resolveGetProjectsQuery(getProjectsQueryResponse);
-
- return waitForPromises();
- }
-
- return Promise.resolve();
- };
-
- const findTokenSelector = () => wrapper.findComponent(GlTokenSelector);
- const findTokenSelectorInput = () => findTokenSelector().find('input[type="text"]');
- const findIntersectionObserver = () => wrapper.findComponent(GlIntersectionObserver);
-
- it('renders dropdown items with project avatars', async () => {
- await createComponent();
-
- wrapper.findAllComponents(GlAvatarLabeled).wrappers.forEach((avatarLabeledWrapper, index) => {
- const project = projects[index];
-
- expect(avatarLabeledWrapper.attributes()).toEqual(
- expect.objectContaining({
- 'entity-id': `${getIdFromGraphQLId(project.id)}`,
- 'entity-name': project.name,
- ...(project.avatarUrl && { src: project.avatarUrl }),
- }),
- );
-
- expect(avatarLabeledWrapper.props()).toEqual(
- expect.objectContaining({
- label: project.name,
- subLabel: project.nameWithNamespace,
- }),
- );
- });
- });
-
- it('renders tokens with project avatars', () => {
- createComponent({
- propsData: {
- selectedProjects: [{ ...project2, id: getIdFromGraphQLId(project2.id) }],
- },
- });
-
- const token = wrapper.findComponent(GlToken);
- const avatar = token.findComponent(GlAvatar);
-
- expect(token.text()).toContain(project2.nameWithNamespace);
- expect(avatar.attributes('src')).toBe(project2.avatarUrl);
- expect(avatar.props()).toEqual(
- expect.objectContaining({
- entityId: getIdFromGraphQLId(project2.id),
- entityName: project2.name,
- }),
- );
- });
-
- describe('when `enter` key is pressed', () => {
- it('calls `preventDefault` so form is not submitted when user selects a project from the dropdown', () => {
- createComponent();
-
- const event = {
- preventDefault: jest.fn(),
- };
-
- findTokenSelectorInput().trigger('keydown.enter', event);
-
- expect(event.preventDefault).toHaveBeenCalled();
- });
- });
-
- describe('when text input is typed in', () => {
- const searchTerm = 'foo bar';
-
- beforeEach(async () => {
- await createComponent();
-
- await findTokenSelectorInput().setValue(searchTerm);
- runDebounce();
- });
-
- it('makes GraphQL request with `search` variable set', async () => {
- expect(getProjectsQueryRequestHandler).toHaveBeenLastCalledWith({
- search: searchTerm,
- after: null,
- first: 20,
- ids: null,
- });
- });
-
- it('sets loading state while waiting for GraphQL request to resolve', async () => {
- expect(findTokenSelector().props('loading')).toBe(true);
-
- resolveGetProjectsQuery(getProjectsQueryResponse);
- await waitForPromises();
-
- expect(findTokenSelector().props('loading')).toBe(false);
- });
- });
-
- describe('when there is a next page of projects and user scrolls to the bottom of the dropdown', () => {
- beforeEach(async () => {
- await createComponent();
-
- findIntersectionObserver().vm.$emit('appear');
- });
-
- it('makes GraphQL request with `after` variable set', async () => {
- expect(getProjectsQueryRequestHandler).toHaveBeenLastCalledWith({
- after: pageInfo.endCursor,
- first: 20,
- search: '',
- ids: null,
- });
- });
-
- it('displays loading icon while waiting for GraphQL request to resolve', async () => {
- expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
-
- resolveGetProjectsQuery(getProjectsQueryResponsePage2);
- await waitForPromises();
-
- expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false);
- });
- });
-
- describe('when there is not a next page of projects', () => {
- it('does not render `GlIntersectionObserver`', async () => {
- createComponent({ resolveQueries: false });
-
- resolveGetProjectsQuery(getProjectsQueryResponsePage2);
- await waitForPromises();
-
- expect(findIntersectionObserver().exists()).toBe(false);
- });
- });
-
- describe('when `GlTokenSelector` emits `input` event', () => {
- it('emits `input` event used by `v-model`', () => {
- findTokenSelector().vm.$emit('input', project1);
-
- expect(wrapper.emitted('input')[0]).toEqual([project1]);
- });
- });
-
- describe('when `GlTokenSelector` emits `focus` event', () => {
- it('emits `focus` event', () => {
- const event = { fakeEvent: 'foo' };
- findTokenSelector().vm.$emit('focus', event);
-
- expect(wrapper.emitted('focus')[0]).toEqual([event]);
- });
- });
-
- describe('when `initialProjectIds` is an empty array', () => {
- it('does not request initial projects', async () => {
- await createComponent();
-
- expect(getProjectsQueryRequestHandler).toHaveBeenCalledTimes(1);
- expect(getProjectsQueryRequestHandler).toHaveBeenCalledWith(
- expect.objectContaining({
- ids: null,
- }),
- );
- });
- });
-
- describe('when `initialProjectIds` is an array of project IDs', () => {
- it('requests those projects and emits `input` event with result', async () => {
- await createComponent({
- propsData: {
- initialProjectIds: [getIdFromGraphQLId(project1.id), getIdFromGraphQLId(project2.id)],
- },
- });
-
- resolveGetInitialProjectsQuery(getProjectsQueryResponse);
- await waitForPromises();
-
- expect(getProjectsQueryRequestHandler).toHaveBeenCalledWith({
- after: '',
- first: null,
- search: '',
- ids: [project1.id, project2.id],
- });
- expect(wrapper.emitted('input')[0][0]).toEqual([
- { ...project1, id: getIdFromGraphQLId(project1.id) },
- { ...project2, id: getIdFromGraphQLId(project2.id) },
- ]);
- });
- });
-});
diff --git a/spec/frontend/access_tokens/index_spec.js b/spec/frontend/access_tokens/index_spec.js
index b6119f1d167..0c611a4a512 100644
--- a/spec/frontend/access_tokens/index_spec.js
+++ b/spec/frontend/access_tokens/index_spec.js
@@ -8,13 +8,11 @@ import {
initAccessTokenTableApp,
initExpiresAtField,
initNewAccessTokenApp,
- initProjectsField,
initTokensApp,
} from '~/access_tokens';
import * as AccessTokenTableApp from '~/access_tokens/components/access_token_table_app.vue';
-import * as ExpiresAtField from '~/access_tokens/components/expires_at_field.vue';
+import ExpiresAtField from '~/access_tokens/components/expires_at_field.vue';
import * as NewAccessTokenApp from '~/access_tokens/components/new_access_token_app.vue';
-import * as ProjectsField from '~/access_tokens/components/projects_field.vue';
import * as TokensApp from '~/access_tokens/components/tokens_app.vue';
import { FEED_TOKEN, INCOMING_EMAIL_TOKEN, STATIC_OBJECT_TOKEN } from '~/access_tokens/constants';
import { __, sprintf } from '~/locale';
@@ -115,49 +113,28 @@ describe('access tokens', () => {
});
});
- describe.each`
- initFunction | mountSelector | fieldName | expectedComponent
- ${initExpiresAtField} | ${'js-access-tokens-expires-at'} | ${'expiresAt'} | ${ExpiresAtField}
- ${initProjectsField} | ${'js-access-tokens-projects'} | ${'projects'} | ${ProjectsField}
- `('$initFunction', ({ initFunction, mountSelector, fieldName, expectedComponent }) => {
+ describe('initExpiresAtField', () => {
describe('when mount element exists', () => {
- const FakeComponent = Vue.component('FakeComponent', {
- props: ['inputAttrs'],
- render: () => null,
- });
-
- const nameAttribute = `access_tokens[${fieldName}]`;
- const idAttribute = `access_tokens_${fieldName}`;
+ const nameAttribute = 'access_tokens[expires_at]';
+ const idAttribute = 'access_tokens_expires_at';
beforeEach(() => {
- window.gon = { features: { personalAccessTokensScopedToProjects: true } };
-
setHTMLFixture(
- `<div class="${mountSelector}">
+ `<div class="js-access-tokens-expires-at">
<input
- name="${nameAttribute}"
- data-js-name="${fieldName}"
- id="${idAttribute}"
+ name="access_tokens[expires_at]"
+ data-js-name="expiresAt"
+ id="access_tokens_expires_at"
placeholder="Foo bar"
value="1,2"
/>
</div>`,
);
-
- // Mock component so we don't have to deal with mocking Apollo
- // eslint-disable-next-line no-param-reassign
- expectedComponent.default = FakeComponent;
- });
-
- afterEach(() => {
- delete window.gon;
});
it('mounts component and sets `inputAttrs` prop', async () => {
- const vueInstance = await initFunction();
-
- wrapper = createWrapper(vueInstance);
- const component = wrapper.findComponent(FakeComponent);
+ wrapper = createWrapper(initExpiresAtField());
+ const component = wrapper.findComponent(ExpiresAtField);
expect(component.exists()).toBe(true);
expect(component.props('inputAttrs')).toEqual({
@@ -171,7 +148,7 @@ describe('access tokens', () => {
describe('when mount element does not exist', () => {
it('returns `null`', () => {
- expect(initFunction()).toBe(null);
+ expect(initExpiresAtField()).toBe(null);
});
});
});
diff --git a/spec/lib/gitlab/error_tracking/error_repository/open_api_strategy_spec.rb b/spec/lib/gitlab/error_tracking/error_repository/open_api_strategy_spec.rb
index 81e2a410962..bcd59c34ea2 100644
--- a/spec/lib/gitlab/error_tracking/error_repository/open_api_strategy_spec.rb
+++ b/spec/lib/gitlab/error_tracking/error_repository/open_api_strategy_spec.rb
@@ -430,7 +430,7 @@ RSpec.describe Gitlab::ErrorTracking::ErrorRepository::OpenApiStrategy do
it do
is_expected
- .to eq("#{config.scheme}://#{public_key}@#{config.host}/errortracking/api/v1/projects/api/#{project.id}")
+ .to eq("#{config.scheme}://#{public_key}@#{config.host}/errortracking/api/v1/projects/#{project.id}")
end
end
end
diff --git a/spec/requests/api/internal/error_tracking_spec.rb b/spec/requests/api/internal/error_tracking_spec.rb
index 69eb54d5ed2..4c420eb8505 100644
--- a/spec/requests/api/internal/error_tracking_spec.rb
+++ b/spec/requests/api/internal/error_tracking_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe API::Internal::ErrorTracking do
let(:secret_token) { Gitlab::CurrentSettings.error_tracking_access_token }
let(:headers) do
- { ::API::Internal::ErrorTracking::GITLAB_ERROR_TRACKING_TOKEN_HEADER => Base64.encode64(secret_token) }
+ { ::API::Internal::ErrorTracking::GITLAB_ERROR_TRACKING_TOKEN_HEADER => secret_token }
end
describe 'GET /internal/error_tracking/allowed' do
diff --git a/spec/services/issues/clone_service_spec.rb b/spec/services/issues/clone_service_spec.rb
index c967f9ad1f9..cabd0d6732c 100644
--- a/spec/services/issues/clone_service_spec.rb
+++ b/spec/services/issues/clone_service_spec.rb
@@ -140,11 +140,11 @@ RSpec.describe Issues::CloneService do
context 'issue with award emoji' do
let!(:award_emoji) { create(:award_emoji, awardable: old_issue) }
- it 'copies the award emoji' do
+ it 'does not copy the award emoji' do
old_issue.reload
new_issue = clone_service.execute(old_issue, new_project)
- expect(old_issue.award_emoji.first.name).to eq new_issue.reload.award_emoji.first.name
+ expect(new_issue.reload.award_emoji).to be_empty
end
end
diff --git a/spec/services/work_items/update_service_spec.rb b/spec/services/work_items/update_service_spec.rb
index b17c9ffb4fb..5fee5926823 100644
--- a/spec/services/work_items/update_service_spec.rb
+++ b/spec/services/work_items/update_service_spec.rb
@@ -14,16 +14,18 @@ RSpec.describe WorkItems::UpdateService do
let(:current_user) { developer }
describe '#execute' do
- subject(:update_work_item) do
+ let(:service) do
described_class.new(
project: project,
current_user: current_user,
params: opts,
spam_params: spam_params,
widget_params: widget_params
- ).execute(work_item)
+ )
end
+ subject(:update_work_item) { service.execute(work_item) }
+
before do
stub_spam_services
end
@@ -142,6 +144,21 @@ RSpec.describe WorkItems::UpdateService do
expect(work_item.description).to eq('changed')
end
+
+ context 'when work item validation fails' do
+ let(:opts) { { title: '' } }
+
+ it 'returns validation errors' do
+ expect(update_work_item[:message]).to contain_exactly("Title can't be blank")
+ end
+
+ it 'does not execute after-update widgets', :aggregate_failures do
+ expect(service).to receive(:update).and_call_original
+ expect(service).not_to receive(:execute_widgets).with(callback: :update, widget_params: widget_params)
+
+ expect { update_work_item }.not_to change(work_item, :description)
+ end
+ end
end
context 'for the hierarchy widget' do
@@ -175,6 +192,22 @@ RSpec.describe WorkItems::UpdateService do
end.to not_change(WorkItems::ParentLink, :count).and(not_change(work_item, :title))
end
end
+
+ context 'when work item validation fails' do
+ let(:opts) { { title: '' } }
+
+ it 'returns validation errors' do
+ expect(update_work_item[:message]).to contain_exactly("Title can't be blank")
+ end
+
+ it 'does not execute after-update widgets', :aggregate_failures do
+ expect(service).to receive(:update).and_call_original
+ expect(service).not_to receive(:execute_widgets).with(callback: :before_update_in_transaction, widget_params: widget_params)
+ expect(work_item.work_item_children).not_to include(child_work_item)
+
+ update_work_item
+ end
+ end
end
end
end
diff --git a/spec/services/work_items/widgets/hierarchy_service/update_service_spec.rb b/spec/services/work_items/widgets/hierarchy_service/update_service_spec.rb
index 4f6ff1b8676..4e155d18fc0 100644
--- a/spec/services/work_items/widgets/hierarchy_service/update_service_spec.rb
+++ b/spec/services/work_items/widgets/hierarchy_service/update_service_spec.rb
@@ -29,6 +29,14 @@ RSpec.describe WorkItems::Widgets::HierarchyService::UpdateService do
end
end
+ context 'when invalid params are present' do
+ let(:params) { { other_parent: parent_work_item } }
+
+ it_behaves_like 'raises a WidgetError' do
+ let(:message) { 'One or more arguments are invalid: other_parent.' }
+ end
+ end
+
context 'when updating children' do
let_it_be(:child_work_item2) { create(:work_item, :task, project: project) }
let_it_be(:child_work_item3) { create(:work_item, :task, project: project) }