Commit a2dfdbba authored by Phil Hughes's avatar Phil Hughes

Merge branch '233479-add-test-case-move-support' into 'master'

Add frontend support for moving test cases between projects

See merge request gitlab-org/gitlab!46447
parents ea0e537b 3ba714b8
......@@ -118,6 +118,12 @@ module Types
field :severity, Types::IssuableSeverityEnum, null: true,
description: 'Severity level of the incident'
field :moved, GraphQL::BOOLEAN_TYPE, method: :moved?, null: true,
description: 'Indicates if issue got moved from other project'
field :moved_to, Types::IssueType, null: true,
description: 'Updated Issue after it got moved to another project'
def user_notes_count
BatchLoader::GraphQL.for(object.id).batch(key: :issue_user_notes_count) do |ids, loader, args|
counts = Note.count_for_collection(ids, 'Issue').index_by(&:noteable_id)
......@@ -150,6 +156,10 @@ module Types
Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, object.milestone_id).find
end
def moved_to
Gitlab::Graphql::Loaders::BatchModelLoader.new(Issue, object.moved_to_id).find
end
def discussion_locked
!!object.discussion_locked
end
......
......@@ -3,4 +3,5 @@
class MoveToProjectEntity < Grape::Entity
expose :id
expose :name_with_namespace
expose :full_path
end
---
title: Expose moved and movedTo attributes in Issues query
merge_request: 46447
author:
type: added
......@@ -7645,6 +7645,16 @@ type EpicIssue implements CurrentUserTodos & Noteable {
"""
milestone: Milestone
"""
Indicates if issue got moved from other project
"""
moved: Boolean
"""
Updated Issue after it got moved to another project
"""
movedTo: Issue
"""
All notes on this noteable
"""
......@@ -10169,6 +10179,16 @@ type Issue implements CurrentUserTodos & Noteable {
"""
milestone: Milestone
"""
Indicates if issue got moved from other project
"""
moved: Boolean
"""
Updated Issue after it got moved to another project
"""
movedTo: Issue
"""
All notes on this noteable
"""
......
......@@ -21168,6 +21168,34 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "moved",
"description": "Indicates if issue got moved from other project",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "movedTo",
"description": "Updated Issue after it got moved to another project",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Issue",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "notes",
"description": "All notes on this noteable",
......@@ -27821,6 +27849,34 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "moved",
"description": "Indicates if issue got moved from other project",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "movedTo",
"description": "Updated Issue after it got moved to another project",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Issue",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "notes",
"description": "All notes on this noteable",
......@@ -1268,6 +1268,8 @@ Relationship between an epic and an issue.
| `iteration` | Iteration | Iteration of the issue |
| `labels` | LabelConnection | Labels of the issue |
| `milestone` | Milestone | Milestone of the issue |
| `moved` | Boolean | Indicates if issue got moved from other project |
| `movedTo` | Issue | Updated Issue after it got moved to another project |
| `notes` | NoteConnection! | All notes on this noteable |
| `participants` | UserConnection | List of participants in the issue |
| `reference` | String! | Internal reference of the issue. Returned in shortened format by default |
......@@ -1534,6 +1536,8 @@ Represents a recorded measurement (object count) for the Admins.
| `iteration` | Iteration | Iteration of the issue |
| `labels` | LabelConnection | Labels of the issue |
| `milestone` | Milestone | Milestone of the issue |
| `moved` | Boolean | Indicates if issue got moved from other project |
| `movedTo` | Issue | Updated Issue after it got moved to another project |
| `notes` | NoteConnection! | All notes on this noteable |
| `participants` | UserConnection | List of participants in the issue |
| `reference` | String! | Internal reference of the issue. Returned in shortened format by default |
......
<script>
import { GlLoadingIcon, GlDropdown, GlDropdownDivider, GlDropdownItem, GlButton } from '@gitlab/ui';
import {
GlLoadingIcon,
GlDropdown,
GlDropdownDivider,
GlDropdownItem,
GlButton,
GlSprintf,
GlLink,
} from '@gitlab/ui';
import { s__, __ } from '~/locale';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
......@@ -21,6 +29,8 @@ export default {
GlDropdownDivider,
GlDropdownItem,
GlButton,
GlSprintf,
GlLink,
IssuableShow,
TestCaseSidebar,
},
......@@ -136,7 +146,17 @@ export default {
@edit-issuable="handleEditTestCase"
>
<template #status-badge>
{{ statusBadgeText }}
<gl-sprintf
v-if="testCase.moved"
:message="__('Archived (%{movedToStart}moved%{movedToEnd})')"
>
<template #movedTo="{ content }">
<gl-link :href="testCase.movedTo.webUrl" class="text-white text-underline">{{
content
}}</gl-link>
</template>
</gl-sprintf>
<span v-else>{{ statusBadgeText }}</span>
</template>
<template #header-actions>
<gl-dropdown
......@@ -194,6 +214,7 @@ export default {
:sidebar-expanded="sidebarExpanded"
:selected-labels="selectedLabels"
:todo="todo"
:moved="testCase.moved"
@test-case-updated="handleTestCaseUpdated"
/>
</template>
......
......@@ -4,6 +4,7 @@ import Mousetrap from 'mousetrap';
import { s__, __ } from '~/locale';
import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
import ProjectSelect from '~/vue_shared/components/sidebar/issuable_move_dropdown.vue';
import TestCaseGraphQL from '../mixins/test_case_graphql';
......@@ -13,6 +14,7 @@ export default {
GlIcon,
GlLoadingIcon,
LabelsSelect,
ProjectSelect,
},
directives: {
GlTooltip,
......@@ -21,8 +23,10 @@ export default {
'projectFullPath',
'testCaseId',
'canEditTestCase',
'canMoveTestCase',
'labelsFetchPath',
'labelsManagePath',
'projectsFetchPath',
],
mixins: [TestCaseGraphQL],
props: {
......@@ -39,6 +43,11 @@ export default {
type: Array,
required: true,
},
moved: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
......@@ -59,8 +68,14 @@ export default {
todoIcon() {
return this.isTodoPending ? 'todo-done' : 'todo-add';
},
selectProjectDropdownButtonTitle() {
return this.testCaseMoveInProgress
? s__('TestCases|Moving test case')
: s__('TestCases|Move test case');
},
},
mounted() {
this.sidebarEl = document.querySelector('aside.right-sidebar');
Mousetrap.bind('l', this.handleLabelsCollapsedButtonClick);
},
beforeDestroy() {
......@@ -77,27 +92,39 @@ export default {
toggleSidebar() {
document.querySelector('.js-toggle-right-sidebar-button').dispatchEvent(new Event('click'));
},
handleLabelsDropdownClose() {
if (this.sidebarExpandedOnClick) {
this.sidebarExpandedOnClick = false;
this.toggleSidebar();
}
},
handleLabelsCollapsedButtonClick() {
expandSidebarAndOpenDropdown(dropdownButtonSelector) {
// Expand the sidebar if not already expanded.
if (!this.sidebarExpanded) {
this.toggleSidebar();
this.sidebarExpandedOnClick = true;
}
// Wait for sidebar expand to complete before
// revealing labels dropdown.
this.$nextTick(() => {
document
.querySelector('.js-labels-block .js-sidebar-dropdown-toggle')
.dispatchEvent(new Event('click', { bubbles: true, cancelable: false }));
// Wait for sidebar expand animation to complete
// before revealing the dropdown.
this.sidebarEl.addEventListener(
'transitionend',
() => {
document
.querySelector(dropdownButtonSelector)
.dispatchEvent(new Event('click', { bubbles: true, cancelable: false }));
},
{ once: true },
);
});
},
handleSidebarDropdownClose() {
if (this.sidebarExpandedOnClick) {
this.sidebarExpandedOnClick = false;
this.toggleSidebar();
}
},
handleLabelsCollapsedButtonClick() {
this.expandSidebarAndOpenDropdown('.js-labels-block .js-sidebar-dropdown-toggle');
},
handleProjectsCollapsedButtonClick() {
this.expandSidebarAndOpenDropdown('.js-issuable-move-block .js-sidebar-dropdown-toggle');
},
handleUpdateSelectedLabels(labels) {
// Iterate over selection and check if labels which were
// either selected or removed aren't leading to same selection
......@@ -170,9 +197,19 @@ export default {
variant="sidebar"
class="block labels js-labels-block"
@updateSelectedLabels="handleUpdateSelectedLabels"
@onDropdownClose="handleLabelsDropdownClose"
@onDropdownClose="handleSidebarDropdownClose"
@toggleCollapse="handleLabelsCollapsedButtonClick"
>{{ __('None') }}</labels-select
>
<project-select
v-if="canMoveTestCase && !moved"
:projects-fetch-path="projectsFetchPath"
:dropdown-button-title="selectProjectDropdownButtonTitle"
:dropdown-header-title="__('Move test case')"
:move-in-progress="testCaseMoveInProgress"
@dropdown-close="handleSidebarDropdownClose"
@toggle-collapse="handleProjectsCollapsedButtonClick"
@move-issuable="moveTestCase"
/>
</div>
</template>
import Api from '~/api';
import createFlash from '~/flash';
import { s__ } from '~/locale';
import { visitUrl } from '~/lib/utils/url_utility';
import projectTestCase from '../queries/project_test_case.query.graphql';
import updateTestCase from '../queries/update_test_case.mutation.graphql';
import markTestCaseTodoDone from '../queries/mark_test_case_todo_done.mutation.graphql';
import moveTestCase from '../queries/move_test_case.mutation.graphql';
export default {
apollo: {
......@@ -38,6 +40,7 @@ export default {
testCaseLoading: true,
testCaseLoadFailed: false,
testCaseTodoUpdateInProgress: false,
testCaseMoveInProgress: false,
};
},
methods: {
......@@ -118,5 +121,36 @@ export default {
this.testCaseTodoUpdateInProgress = false;
});
},
moveTestCase(targetProject) {
this.testCaseMoveInProgress = true;
return this.$apollo
.mutate({
mutation: moveTestCase,
variables: {
moveTestCaseInput: {
projectPath: this.projectFullPath,
iid: this.testCaseId,
targetProjectPath: targetProject.full_path,
},
},
})
.then(({ data = {} }) => {
if (!data.issueMove) return;
const { errors } = data.issueMove;
if (errors?.length) {
throw new Error(`Error moving test case. Error message: ${errors[0].message}`);
}
visitUrl(data.issueMove?.issue.webUrl);
})
.catch(error => {
this.testCaseMoveInProgress = false;
createFlash({
message: s__('TestCases|Something went wrong while moving test case.'),
captureError: true,
error,
});
});
},
},
};
mutation moveTestCase($moveTestCaseInput: IssueMoveInput!) {
issueMove(input: $moveTestCaseInput) {
errors
clientMutationId
issue {
webUrl
}
}
}
......@@ -16,6 +16,10 @@ fragment TestCase on Issue {
webUrl
blocked
confidential
moved
movedTo {
webUrl
}
author {
...Author
}
......
......@@ -19,11 +19,14 @@ export default function initTestCaseShow({ mountPointSelector }) {
defaultClient: createDefaultClient(),
});
const sidebarOptions = JSON.parse(el.dataset.sidebarOptions);
return new Vue({
el,
apolloProvider,
provide: {
...el.dataset,
projectsFetchPath: sidebarOptions.projectsAutocompleteEndpoint,
canEditTestCase: parseBoolean(el.dataset.canEditTestCase),
},
render: createElement => createElement(TestCaseShowApp),
......
......@@ -7,10 +7,12 @@
#js-issuable-app{ data: { initial: issuable_initial_data(@issue).to_json,
can_edit_test_case: can?(current_user, :admin_issue, @project).to_s,
can_move_test_case: @issuable_sidebar.dig(:current_user, :can_move).to_s,
description_preview_path: preview_markdown_path(@project),
description_help_path: help_page_path('user/markdown'),
project_full_path: @project.full_path,
labels_manage_path: project_labels_path(@project),
labels_fetch_path: project_labels_path(@project, format: :json),
test_case_new_path: new_project_quality_test_case_path(@project),
sidebar_options: issuable_sidebar_options(@issuable_sidebar).to_json.html_safe,
test_case_id: @issue.iid } }
import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
import { GlLink, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
import { mockCurrentUserTodo } from 'jest/issuable_list/mock_data';
......@@ -33,6 +33,7 @@ const createComponent = ({ testCase, testCaseQueryLoading = false } = {}) =>
},
},
stubs: {
GlSprintf,
IssuableShow,
IssuableHeader,
IssuableBody,
......@@ -301,6 +302,27 @@ describe('TestCaseShowRoot', () => {
expect(wrapper.find('[data-testid="status"]').text()).toContain('Open');
});
it('renders status-badge slot contents with updated test case URL when testCase.moved is true', () => {
const movedTestCase = {
...mockTestCase,
status: 'closed',
moved: true,
movedTo: {
webUrl: 'http://0.0.0.0:3000/gitlab-org/gitlab-test/-/issues/30',
},
};
const wrapperMoved = createComponent({
testCase: movedTestCase,
});
const statusEl = wrapperMoved.find('[data-testid="status"]');
expect(statusEl.text()).toContain('Archived');
expect(statusEl.find(GlLink).attributes('href')).toBe(movedTestCase.movedTo.webUrl);
wrapperMoved.destroy();
});
it('renders header-actions slot contents', () => {
expect(wrapper.find('[data-testid="actions-dropdown"]').exists()).toBe(true);
expect(wrapper.find('[data-testid="archive-test-case"]').exists()).toBe(true);
......
......@@ -7,6 +7,7 @@ import { mockCurrentUserTodo, mockLabels } from 'jest/issuable_list/mock_data';
import TestCaseSidebar from 'ee/test_case_show/components/test_case_sidebar.vue';
import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
import ProjectSelect from '~/vue_shared/components/sidebar/issuable_move_dropdown.vue';
import { mockProvide, mockTestCase } from '../mock_data';
......@@ -41,6 +42,7 @@ describe('TestCaseSidebar', () => {
let wrapper;
beforeEach(() => {
setFixtures('<aside class="right-sidebar"></aside>');
mousetrapSpy = jest.spyOn(Mousetrap, 'bind');
wrapper = createComponent();
});
......@@ -75,6 +77,25 @@ describe('TestCaseSidebar', () => {
expect(wrapper.vm[propName]).toBe(propValue);
});
});
describe('selectProjectDropdownButtonTitle', () => {
it.each`
testCaseMoveInProgress | returnValue
${true} | ${'Moving test case'}
${false} | ${'Move test case'}
`(
'returns $returnValue when testCaseMoveInProgress is $testCaseMoveInProgress',
async ({ testCaseMoveInProgress, returnValue }) => {
wrapper.setData({
testCaseMoveInProgress,
});
await wrapper.vm.$nextTick();
expect(wrapper.vm.selectProjectDropdownButtonTitle).toBe(returnValue);
},
);
});
});
describe('mounted', () => {
......@@ -129,23 +150,7 @@ describe('TestCaseSidebar', () => {
});
});
describe('handleLabelsDropdownClose', () => {
it('sets `sidebarExpandedOnClick` to false and calls `toggleSidebar` method when `sidebarExpandedOnClick` is true', async () => {
jest.spyOn(wrapper.vm, 'toggleSidebar').mockImplementation(jest.fn());
wrapper.setData({
sidebarExpandedOnClick: true,
});
await wrapper.vm.$nextTick();
wrapper.vm.handleLabelsDropdownClose();
expect(wrapper.vm.sidebarExpandedOnClick).toBe(false);
expect(wrapper.vm.toggleSidebar).toHaveBeenCalled();
});
});
describe('handleLabelsCollapsedButtonClick', () => {
describe('expandSidebarAndOpenDropdown', () => {
beforeEach(() => {
setFixtures(`
<div class="js-labels-block">
......@@ -162,7 +167,7 @@ describe('TestCaseSidebar', () => {
await wrapper.vm.$nextTick();
wrapper.vm.handleLabelsCollapsedButtonClick();
wrapper.vm.expandSidebarAndOpenDropdown('.js-labels-block .js-sidebar-dropdown-toggle');
expect(wrapper.vm.toggleSidebar).toHaveBeenCalled();
expect(wrapper.vm.sidebarExpandedOnClick).toBe(true);
......@@ -178,10 +183,12 @@ describe('TestCaseSidebar', () => {
await wrapper.vm.$nextTick();
wrapper.vm.handleLabelsCollapsedButtonClick();
wrapper.vm.expandSidebarAndOpenDropdown('.js-labels-block .js-sidebar-dropdown-toggle');
await wrapper.vm.$nextTick();
wrapper.vm.sidebarEl.dispatchEvent(new Event('transitionend'));
expect(buttonEl.dispatchEvent).toHaveBeenCalledWith(
expect.objectContaining({
type: 'click',
......@@ -192,6 +199,22 @@ describe('TestCaseSidebar', () => {
});
});
describe('handleSidebarDropdownClose', () => {
it('sets `sidebarExpandedOnClick` to false and calls `toggleSidebar` method when `sidebarExpandedOnClick` is true', async () => {
jest.spyOn(wrapper.vm, 'toggleSidebar').mockImplementation(jest.fn());
wrapper.setData({
sidebarExpandedOnClick: true,
});
await wrapper.vm.$nextTick();
wrapper.vm.handleSidebarDropdownClose();
expect(wrapper.vm.sidebarExpandedOnClick).toBe(false);
expect(wrapper.vm.toggleSidebar).toHaveBeenCalled();
});
});
describe('handleUpdateSelectedLabels', () => {
const updatedLabels = [
{
......@@ -281,5 +304,19 @@ describe('TestCaseSidebar', () => {
});
expect(labelSelectEl.text()).toBe('None');
});
it('renders project-select', async () => {
const { selectProjectDropdownButtonTitle, testCaseMoveInProgress } = wrapper.vm;
const { projectsFetchPath } = mockProvide;
const projectSelectEl = wrapper.find(ProjectSelect);
expect(projectSelectEl.exists()).toBe(true);
expect(projectSelectEl.props()).toMatchObject({
projectsFetchPath,
dropdownButtonTitle: selectProjectDropdownButtonTitle,
dropdownHeaderTitle: 'Move test case',
moveInProgress: testCaseMoveInProgress,
});
});
});
});
......@@ -5,13 +5,16 @@ import { mockCurrentUserTodo } from 'jest/issuable_list/mock_data';
import TestCaseShowRoot from 'ee/test_case_show/components/test_case_show_root.vue';
import updateTestCase from 'ee/test_case_show/queries/update_test_case.mutation.graphql';
import markTestCaseTodoDone from 'ee/test_case_show/queries/mark_test_case_todo_done.mutation.graphql';
import moveTestCase from 'ee/test_case_show/queries/move_test_case.mutation.graphql';
import createFlash from '~/flash';
import Api from '~/api';
import { visitUrl } from '~/lib/utils/url_utility';
import { mockProvide, mockTestCase } from '../mock_data';
jest.mock('~/flash');
jest.mock('~/lib/utils/url_utility');
const createComponent = ({ testCase, testCaseQueryLoading = false } = {}) =>
shallowMount(TestCaseShowRoot, {
......@@ -214,4 +217,67 @@ describe('TestCaseGraphQL Mixin', () => {
});
});
});
describe('moveTestCase', () => {
const mockTargetProject = {
full_path: 'gitlab-org/gitlab-shell',
};
const moveResolvedMutation = {
data: {
issueMove: {
errors: [],
issue: {
webUrl: mockTestCase.webUrl,
},
},
},
};
it('sets `testCaseMoveInProgress` to true', () => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(moveResolvedMutation);
wrapper.vm.moveTestCase(mockTargetProject);
expect(wrapper.vm.testCaseMoveInProgress).toBe(true);
});
it('calls `$apollo.mutate` with moveTestCase mutation and moveTestCaseInput variables', () => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(moveResolvedMutation);
wrapper.vm.moveTestCase(mockTargetProject);
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: moveTestCase,
variables: {
moveTestCaseInput: {
projectPath: mockProvide.projectFullPath,
iid: mockProvide.testCaseId,
targetProjectPath: mockTargetProject.full_path,
},
},
});
});
it('calls `visitUrl` with updated test case URL on mutation promise resolve', async () => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(moveResolvedMutation);
await wrapper.vm.moveTestCase(mockTargetProject);
expect(wrapper.vm.testCaseMoveInProgress).toBe(true);
expect(visitUrl).toHaveBeenCalledWith(moveResolvedMutation.data.issueMove.issue.webUrl);
});
it('calls `createFlash` with errorMessage on mutation promise reject', async () => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue({});
await wrapper.vm.moveTestCase(mockTargetProject);
expect(createFlash).toHaveBeenCalledWith({
message: 'Something went wrong while moving test case.',
captureError: true,
error: expect.any(Object),
});
expect(wrapper.vm.testCaseMoveInProgress).toBe(false);
});
});
});
......@@ -2,6 +2,7 @@ import { mockIssuable, mockCurrentUserTodo } from 'jest/issuable_list/mock_data'
export const mockTestCase = {
...mockIssuable,
moved: false,
currentUserTodos: {
nodes: [mockCurrentUserTodo],
},
......@@ -12,8 +13,10 @@ export const mockProvide = {
testCaseNewPath: '/gitlab-org/gitlab-test/-/quality/test_cases/new',
testCaseId: mockIssuable.iid,
canEditTestCase: true,
canMoveTestCase: true,
descriptionPreviewPath: '/gitlab-org/gitlab-test/preview_markdown',
descriptionHelpPath: '/help/user/markdown',
labelsFetchPath: '/gitlab-org/gitlab-test/-/labels.json',
labelsManagePath: '/gitlab-org/gitlab-shell/-/labels',
projectsFetchPath: '/-/autocomplete/projects?project_id=1',
};
......@@ -3546,6 +3546,9 @@ msgstr ""
msgid "Archived"
msgstr ""
msgid "Archived (%{movedToStart}moved%{movedToEnd})"
msgstr ""
msgid "Archived in this version"
msgstr ""
......@@ -17691,6 +17694,9 @@ msgstr ""
msgid "Move selection up"
msgstr ""
msgid "Move test case"
msgstr ""
msgid "Move this issue to another project."
msgstr ""
......@@ -26495,6 +26501,12 @@ msgstr[1] ""
msgid "Test settings"
msgstr ""
msgid "TestCases|Move test case"
msgstr ""
msgid "TestCases|Moving test case"
msgstr ""
msgid "TestCases|New Test Case"
msgstr ""
......@@ -26522,6 +26534,9 @@ msgstr ""
msgid "TestCases|Something went wrong while marking test case todo as done."
msgstr ""
msgid "TestCases|Something went wrong while moving test case."
msgstr ""
msgid "TestCases|Something went wrong while updating the test case labels."
msgstr ""
......
......@@ -17,7 +17,7 @@ RSpec.describe GitlabSchema.types['Issue'] do
fields = %i[id iid title description state reference author assignees updated_by participants labels milestone due_date
confidential discussion_locked upvotes downvotes user_notes_count user_discussions_count web_path web_url relative_position
emails_disabled subscribed time_estimate total_time_spent human_time_estimate human_total_time_spent closed_at created_at updated_at task_completion_status
designs design_collection alert_management_alert severity current_user_todos]
designs design_collection alert_management_alert severity current_user_todos moved moved_to]
fields.each do |field_name|
expect(described_class).to have_graphql_field(field_name)
......
......@@ -83,6 +83,25 @@ RSpec.describe 'Query.issue(id)' do
end
end
context 'when issue got moved' do
let_it_be(:issue_fields) { ['moved', 'movedTo { title }'] }
let_it_be(:new_issue) { create(:issue) }
let_it_be(:issue) { create(:issue, project: project, moved_to: new_issue) }
let_it_be(:issue_params) { { 'id' => issue.to_global_id.to_s } }
before_all do
new_issue.project.add_developer(current_user)
end
it 'returns correct attributes' do
post_graphql(query, current_user: current_user)
expect(issue_data.keys).to eq( %w(moved movedTo) )
expect(issue_data['moved']).to eq(true)
expect(issue_data['movedTo']['title']).to eq(new_issue.title)
end
end
context 'when passed a non-Issue gid' do
let(:mr) {create(:merge_request)}
......
......@@ -12,8 +12,12 @@ RSpec.describe MoveToProjectEntity do
expect(subject[:id]).to eq(project.id)
end
it 'includes the full path' do
it 'includes the human-readable full path' do
expect(subject[:name_with_namespace]).to eq(project.name_with_namespace)
end
it 'includes the full path' do
expect(subject[:full_path]).to eq(project.full_path)
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment