Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
cbc5129a
Commit
cbc5129a
authored
Oct 19, 2020
by
Kushal Pandya
Committed by
Phil Hughes
Oct 19, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add Test Case Show app
Adds `test_case_show` app to render individual test case.
parent
2ae8ab3a
Changes
21
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
1719 additions
and
13 deletions
+1719
-13
app/assets/javascripts/api.js
app/assets/javascripts/api.js
+9
-0
app/assets/javascripts/issuable_show/constants.js
app/assets/javascripts/issuable_show/constants.js
+5
-0
app/assets/javascripts/pages/projects/issues/show.js
app/assets/javascripts/pages/projects/issues/show.js
+19
-10
ee/app/assets/javascripts/pages/projects/issues/show/index.js
...pp/assets/javascripts/pages/projects/issues/show/index.js
+14
-0
ee/app/assets/javascripts/test_case_show/components/test_case_show_root.vue
...scripts/test_case_show/components/test_case_show_root.vue
+202
-0
ee/app/assets/javascripts/test_case_show/components/test_case_sidebar.vue
...vascripts/test_case_show/components/test_case_sidebar.vue
+178
-0
ee/app/assets/javascripts/test_case_show/mixins/test_case_graphql.js
...ts/javascripts/test_case_show/mixins/test_case_graphql.js
+122
-0
ee/app/assets/javascripts/test_case_show/queries/mark_test_case_todo_done.mutation.graphql
...se_show/queries/mark_test_case_todo_done.mutation.graphql
+9
-0
ee/app/assets/javascripts/test_case_show/queries/project_test_case.query.graphql
...ts/test_case_show/queries/project_test_case.query.graphql
+10
-0
ee/app/assets/javascripts/test_case_show/queries/test_case.fragment.graphql
...scripts/test_case_show/queries/test_case.fragment.graphql
+30
-0
ee/app/assets/javascripts/test_case_show/queries/update_test_case.mutation.graphql
.../test_case_show/queries/update_test_case.mutation.graphql
+11
-0
ee/app/assets/javascripts/test_case_show/test_case_show_bundle.js
...ssets/javascripts/test_case_show/test_case_show_bundle.js
+31
-0
ee/app/views/projects/quality/test_cases/_show.html.haml
ee/app/views/projects/quality/test_cases/_show.html.haml
+9
-2
ee/spec/features/projects/quality/test_case_show_spec.rb
ee/spec/features/projects/quality/test_case_show_spec.rb
+173
-0
ee/spec/frontend/test_case_show/components/test_case_show_root_spec.js
...end/test_case_show/components/test_case_show_root_spec.js
+325
-0
ee/spec/frontend/test_case_show/components/test_case_sidebar_spec.js
...ntend/test_case_show/components/test_case_sidebar_spec.js
+285
-0
ee/spec/frontend/test_case_show/mixins/test_case_graphql_spec.js
.../frontend/test_case_show/mixins/test_case_graphql_spec.js
+217
-0
ee/spec/frontend/test_case_show/mock_data.js
ee/spec/frontend/test_case_show/mock_data.js
+19
-0
locale/gitlab.pot
locale/gitlab.pot
+21
-0
spec/frontend/api_spec.js
spec/frontend/api_spec.js
+19
-0
spec/frontend/issuable_list/mock_data.js
spec/frontend/issuable_list/mock_data.js
+11
-1
No files found.
app/assets/javascripts/api.js
View file @
cbc5129a
...
@@ -30,6 +30,7 @@ const Api = {
...
@@ -30,6 +30,7 @@ const Api = {
projectProtectedBranchesPath
:
'
/api/:version/projects/:id/protected_branches
'
,
projectProtectedBranchesPath
:
'
/api/:version/projects/:id/protected_branches
'
,
projectSearchPath
:
'
/api/:version/projects/:id/search
'
,
projectSearchPath
:
'
/api/:version/projects/:id/search
'
,
projectMilestonesPath
:
'
/api/:version/projects/:id/milestones
'
,
projectMilestonesPath
:
'
/api/:version/projects/:id/milestones
'
,
projectIssuePath
:
'
/api/:version/projects/:id/issues/:issue_iid
'
,
mergeRequestsPath
:
'
/api/:version/merge_requests
'
,
mergeRequestsPath
:
'
/api/:version/merge_requests
'
,
groupLabelsPath
:
'
/groups/:namespace_path/-/labels
'
,
groupLabelsPath
:
'
/groups/:namespace_path/-/labels
'
,
issuableTemplatePath
:
'
/:namespace_path/:project_path/templates/:type/:key
'
,
issuableTemplatePath
:
'
/:namespace_path/:project_path/templates/:type/:key
'
,
...
@@ -328,6 +329,14 @@ const Api = {
...
@@ -328,6 +329,14 @@ const Api = {
});
});
},
},
addProjectIssueAsTodo
(
projectId
,
issueIid
)
{
const
url
=
Api
.
buildUrl
(
Api
.
projectIssuePath
)
.
replace
(
'
:id
'
,
encodeURIComponent
(
projectId
))
.
replace
(
'
:issue_iid
'
,
encodeURIComponent
(
issueIid
));
return
axios
.
post
(
`
${
url
}
/todo`
);
},
mergeRequests
(
params
=
{})
{
mergeRequests
(
params
=
{})
{
const
url
=
Api
.
buildUrl
(
Api
.
mergeRequestsPath
);
const
url
=
Api
.
buildUrl
(
Api
.
mergeRequestsPath
);
...
...
app/assets/javascripts/issuable_show/constants.js
0 → 100644
View file @
cbc5129a
export
const
IssuableType
=
{
Issue
:
'
issue
'
,
Incident
:
'
incident
'
,
TestCase
:
'
test_case
'
,
};
app/assets/javascripts/pages/projects/issues/show.js
View file @
cbc5129a
...
@@ -14,13 +14,20 @@ import { parseIssuableData } from '~/issue_show/utils/parse_data';
...
@@ -14,13 +14,20 @@ import { parseIssuableData } from '~/issue_show/utils/parse_data';
import
initInviteMemberTrigger
from
'
~/invite_member/init_invite_member_trigger
'
;
import
initInviteMemberTrigger
from
'
~/invite_member/init_invite_member_trigger
'
;
import
initInviteMemberModal
from
'
~/invite_member/init_invite_member_modal
'
;
import
initInviteMemberModal
from
'
~/invite_member/init_invite_member_modal
'
;
import
{
IssuableType
}
from
'
~/issuable_show/constants
'
;
export
default
function
()
{
export
default
function
()
{
const
{
issueType
,
...
issuableData
}
=
parseIssuableData
();
const
{
issueType
,
...
issuableData
}
=
parseIssuableData
();
if
(
issueType
===
'
incident
'
)
{
switch
(
issueType
)
{
initIncidentApp
(
issuableData
);
case
IssuableType
.
Incident
:
}
else
if
(
issueType
===
'
issue
'
)
{
initIncidentApp
(
issuableData
);
initIssueApp
(
issuableData
);
break
;
case
IssuableType
.
Issue
:
initIssueApp
(
issuableData
);
break
;
default
:
break
;
}
}
initIssuableHeaderWarning
(
store
);
initIssuableHeaderWarning
(
store
);
...
@@ -31,12 +38,14 @@ export default function() {
...
@@ -31,12 +38,14 @@ export default function() {
.
then
(
module
=>
module
.
default
())
.
then
(
module
=>
module
.
default
())
.
catch
(()
=>
{});
.
catch
(()
=>
{});
new
Issue
();
// eslint-disable-line no-new
new
ShortcutsIssuable
();
// eslint-disable-line no-new
new
ZenMode
();
// eslint-disable-line no-new
new
ZenMode
();
// eslint-disable-line no-new
initIssuableSidebar
();
loadAwardsHandler
();
if
(
issueType
!==
IssuableType
.
TestCase
)
{
initInviteMemberModal
();
new
Issue
();
// eslint-disable-line no-new
initInviteMemberTrigger
();
new
ShortcutsIssuable
();
// eslint-disable-line no-new
initIssuableSidebar
();
loadAwardsHandler
();
initInviteMemberModal
();
initInviteMemberTrigger
();
}
}
}
ee/app/assets/javascripts/pages/projects/issues/show/index.js
View file @
cbc5129a
import
initSidebarBundle
from
'
ee/sidebar/sidebar_bundle
'
;
import
initSidebarBundle
from
'
ee/sidebar/sidebar_bundle
'
;
import
trackShowInviteMemberLink
from
'
ee/projects/track_invite_members
'
;
import
trackShowInviteMemberLink
from
'
ee/projects/track_invite_members
'
;
import
initTestCaseShow
from
'
ee/test_case_show/test_case_show_bundle
'
;
import
{
parseIssuableData
}
from
'
~/issue_show/utils/parse_data
'
;
import
initRelatedIssues
from
'
~/related_issues
'
;
import
initRelatedIssues
from
'
~/related_issues
'
;
import
initShow
from
'
~/pages/projects/issues/show
'
;
import
initShow
from
'
~/pages/projects/issues/show
'
;
import
UserCallout
from
'
~/user_callout
'
;
import
UserCallout
from
'
~/user_callout
'
;
import
{
IssuableType
}
from
'
~/issuable_show/constants
'
;
const
{
issueType
}
=
parseIssuableData
();
initShow
();
initShow
();
if
(
issueType
===
IssuableType
.
TestCase
)
{
initTestCaseShow
({
mountPointSelector
:
'
#js-issuable-app
'
,
});
}
if
(
gon
.
features
&&
!
gon
.
features
.
vueIssuableSidebar
)
{
if
(
gon
.
features
&&
!
gon
.
features
.
vueIssuableSidebar
)
{
initSidebarBundle
();
initSidebarBundle
();
}
}
...
...
ee/app/assets/javascripts/test_case_show/components/test_case_show_root.vue
0 → 100644
View file @
cbc5129a
<
script
>
import
{
GlLoadingIcon
,
GlDropdown
,
GlDropdownDivider
,
GlDropdownItem
,
GlButton
}
from
'
@gitlab/ui
'
;
import
{
s__
,
__
}
from
'
~/locale
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
IssuableShow
from
'
~/issuable_show/components/issuable_show_root.vue
'
;
import
IssuableEventHub
from
'
~/issuable_show/event_hub
'
;
import
TestCaseSidebar
from
'
./test_case_sidebar.vue
'
;
import
TestCaseGraphQL
from
'
../mixins/test_case_graphql
'
;
const
stateEvent
=
{
Close
:
'
CLOSE
'
,
Reopen
:
'
REOPEN
'
,
};
export
default
{
components
:
{
GlLoadingIcon
,
GlDropdown
,
GlDropdownDivider
,
GlDropdownItem
,
GlButton
,
IssuableShow
,
TestCaseSidebar
,
},
inject
:
[
'
projectFullPath
'
,
'
testCaseNewPath
'
,
'
testCaseId
'
,
'
canEditTestCase
'
,
'
descriptionPreviewPath
'
,
'
descriptionHelpPath
'
,
],
mixins
:
[
TestCaseGraphQL
],
data
()
{
return
{
testCase
:
{},
editTestCaseFormVisible
:
false
,
testCaseSaveInProgress
:
false
,
testCaseStateChangeInProgress
:
false
,
};
},
computed
:
{
isTestCaseOpen
()
{
return
this
.
testCase
.
state
===
'
opened
'
;
},
statusBadgeClass
()
{
return
this
.
isTestCaseOpen
?
'
status-box-open
'
:
'
status-box-issue-closed
'
;
},
statusIcon
()
{
return
this
.
isTestCaseOpen
?
'
issue-open-m
'
:
'
mobile-issue-close
'
;
},
statusBadgeText
()
{
return
this
.
isTestCaseOpen
?
__
(
'
Open
'
)
:
__
(
'
Archived
'
);
},
testCaseActionButtonVariant
()
{
return
this
.
isTestCaseOpen
?
'
warning
'
:
'
default
'
;
},
testCaseActionTitle
()
{
return
this
.
isTestCaseOpen
?
__
(
'
Archive test case
'
)
:
__
(
'
Reopen test case
'
);
},
todo
()
{
const
todos
=
this
.
testCase
.
currentUserTodos
.
nodes
;
return
todos
.
length
?
todos
[
0
]
:
null
;
},
selectedLabels
()
{
return
this
.
testCase
.
labels
.
nodes
.
map
(
label
=>
({
...
label
,
id
:
getIdFromGraphQLId
(
label
.
id
),
}));
},
},
methods
:
{
handleTestCaseStateChange
()
{
this
.
testCaseStateChangeInProgress
=
true
;
return
this
.
updateTestCase
({
variables
:
{
stateEvent
:
this
.
isTestCaseOpen
?
stateEvent
.
Close
:
stateEvent
.
Reopen
,
},
errorMessage
:
s__
(
'
TestCases|Something went wrong while updating the test case.
'
),
})
.
then
(
updatedTestCase
=>
{
this
.
testCase
=
updatedTestCase
;
})
.
finally
(()
=>
{
this
.
testCaseStateChangeInProgress
=
false
;
});
},
handleEditTestCase
()
{
this
.
editTestCaseFormVisible
=
true
;
},
handleSaveTestCase
({
issuableTitle
,
issuableDescription
})
{
this
.
testCaseSaveInProgress
=
true
;
return
this
.
updateTestCase
({
variables
:
{
title
:
issuableTitle
,
description
:
issuableDescription
,
},
errorMessage
:
s__
(
'
TestCases|Something went wrong while updating the test case.
'
),
})
.
then
(
updatedTestCase
=>
{
this
.
testCase
=
updatedTestCase
;
this
.
editTestCaseFormVisible
=
false
;
IssuableEventHub
.
$emit
(
'
update.issuable
'
);
})
.
finally
(()
=>
{
this
.
testCaseSaveInProgress
=
false
;
});
},
handleCancelClick
()
{
this
.
editTestCaseFormVisible
=
false
;
IssuableEventHub
.
$emit
(
'
close.form
'
);
},
handleTestCaseUpdated
(
updatedTestCase
)
{
this
.
testCase
=
updatedTestCase
;
},
},
};
</
script
>
<
template
>
<div
class=
"test-case-container"
>
<gl-loading-icon
v-if=
"testCaseLoading"
size=
"md"
class=
"gl-mt-3"
/>
<issuable-show
v-if=
"!testCaseLoading && !testCaseLoadFailed"
:issuable=
"testCase"
:status-badge-class=
"statusBadgeClass"
:status-icon=
"statusIcon"
:enable-edit=
"canEditTestCase"
:enable-autocomplete=
"true"
:edit-form-visible=
"editTestCaseFormVisible"
:description-preview-path=
"descriptionPreviewPath"
:description-help-path=
"descriptionHelpPath"
@
edit-issuable=
"handleEditTestCase"
>
<template
#status-badge
>
{{
statusBadgeText
}}
</
template
>
<
template
#header-actions
>
<gl-dropdown
v-if=
"canEditTestCase"
data-testid=
"actions-dropdown"
:text=
"__('Options')"
:right=
"true"
class=
"d-md-none d-lg-none d-xl-none gl-flex-grow-1"
>
<gl-dropdown-item>
{{
testCaseActionTitle
}}
</gl-dropdown-item>
<gl-dropdown-divider
/>
<gl-dropdown-item
:href=
"testCaseNewPath"
>
{{
__
(
'
New test case
'
)
}}
</gl-dropdown-item>
</gl-dropdown>
<gl-button
v-if=
"canEditTestCase"
data-testid=
"archive-test-case"
category=
"secondary"
class=
"d-none d-sm-none d-md-inline-block gl-mr-2"
:variant=
"testCaseActionButtonVariant"
:loading=
"testCaseStateChangeInProgress"
@
click=
"handleTestCaseStateChange"
>
{{
testCaseActionTitle
}}
</gl-button
>
<gl-button
data-testid=
"new-test-case"
category=
"secondary"
variant=
"success"
class=
"d-md-inline-block"
:class=
"
{ 'd-none d-sm-none': canEditTestCase, 'gl-flex-grow-1': !canEditTestCase }"
:href="testCaseNewPath"
>
{{
__
(
'
New test case
'
)
}}
</gl-button
>
</
template
>
<
template
#edit-form-actions=
"issuableMeta"
>
<gl-button
data-testid=
"save-test-case"
:disable=
"testCaseSaveInProgress || !issuableMeta.issuableTitle.length"
:loading=
"testCaseSaveInProgress"
category=
"primary"
variant=
"success"
class=
"float-left qa-save-button"
@
click.prevent=
"handleSaveTestCase(issuableMeta)"
>
{{
__
(
'
Save changes
'
)
}}
</gl-button
>
<gl-button
data-testid=
"cancel-test-case-edit"
class=
"float-right"
@
click=
"handleCancelClick"
>
{{
__
(
'
Cancel
'
)
}}
</gl-button>
</
template
>
<
template
#right-sidebar-items=
"{ sidebarExpanded }"
>
<test-case-sidebar
:sidebar-expanded=
"sidebarExpanded"
:selected-labels=
"selectedLabels"
:todo=
"todo"
@
test-case-updated=
"handleTestCaseUpdated"
/>
</
template
>
</issuable-show>
</div>
</template>
ee/app/assets/javascripts/test_case_show/components/test_case_sidebar.vue
0 → 100644
View file @
cbc5129a
<
script
>
import
{
GlTooltipDirective
as
GlTooltip
,
GlButton
,
GlIcon
,
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
Mousetrap
from
'
mousetrap
'
;
import
{
s__
,
__
}
from
'
~/locale
'
;
import
LabelsSelect
from
'
~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue
'
;
import
TestCaseGraphQL
from
'
../mixins/test_case_graphql
'
;
export
default
{
components
:
{
GlButton
,
GlIcon
,
GlLoadingIcon
,
LabelsSelect
,
},
directives
:
{
GlTooltip
,
},
inject
:
[
'
projectFullPath
'
,
'
testCaseId
'
,
'
canEditTestCase
'
,
'
labelsFetchPath
'
,
'
labelsManagePath
'
,
],
mixins
:
[
TestCaseGraphQL
],
props
:
{
sidebarExpanded
:
{
type
:
Boolean
,
required
:
true
,
},
todo
:
{
type
:
Object
,
required
:
false
,
default
:
null
,
},
selectedLabels
:
{
type
:
Array
,
required
:
true
,
},
},
data
()
{
return
{
sidebarExpandedOnClick
:
false
,
testCaseLabelsSelectInProgress
:
false
,
};
},
computed
:
{
isTodoPending
()
{
return
this
.
todo
?.
state
===
'
pending
'
;
},
todoUpdateInProgress
()
{
return
this
.
$apollo
.
queries
.
testCase
.
loading
||
this
.
testCaseTodoUpdateInProgress
;
},
todoActionText
()
{
return
this
.
isTodoPending
?
__
(
'
Mark as done
'
)
:
__
(
'
Add a to do
'
);
},
todoIcon
()
{
return
this
.
isTodoPending
?
'
todo-done
'
:
'
todo-add
'
;
},
},
mounted
()
{
Mousetrap
.
bind
(
'
l
'
,
this
.
handleLabelsCollapsedButtonClick
);
},
beforeDestroy
()
{
Mousetrap
.
unbind
(
'
l
'
);
},
methods
:
{
handleTodoButtonClick
()
{
if
(
this
.
isTodoPending
)
{
this
.
markTestCaseTodoDone
();
}
else
{
this
.
addTestCaseAsTodo
();
}
},
toggleSidebar
()
{
document
.
querySelector
(
'
.js-toggle-right-sidebar-button
'
).
dispatchEvent
(
new
Event
(
'
click
'
));
},
handleLabelsDropdownClose
()
{
if
(
this
.
sidebarExpandedOnClick
)
{
this
.
sidebarExpandedOnClick
=
false
;
this
.
toggleSidebar
();
}
},
handleLabelsCollapsedButtonClick
()
{
// 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
}));
});
},
handleUpdateSelectedLabels
(
labels
)
{
// Iterate over selection and check if labels which were
// either selected or removed aren't leading to same selection
// as current one, as then we don't want to make network call
// since nothing has changed.
const
anyLabelUpdated
=
labels
.
some
(
label
=>
{
// Find this label in existing selection.
const
existingLabel
=
this
.
selectedLabels
.
find
(
l
=>
l
.
id
===
label
.
id
);
// Check either of the two following conditions;
// 1. A label that's not currently applied is being applied.
// 2. A label that's already applied is being removed.
return
(
!
existingLabel
&&
label
.
set
)
||
(
existingLabel
&&
!
label
.
set
);
});
// Only proceed with action if there are any label updates to be done.
if
(
anyLabelUpdated
)
{
this
.
testCaseLabelsSelectInProgress
=
true
;
return
this
.
updateTestCase
({
variables
:
{
addLabelIds
:
labels
.
filter
(
label
=>
label
.
set
).
map
(
label
=>
label
.
id
),
removeLabelIds
:
labels
.
filter
(
label
=>
!
label
.
set
).
map
(
label
=>
label
.
id
),
},
errorMessage
:
s__
(
'
TestCases|Something went wrong while updating the test case labels.
'
),
})
.
then
(
updatedTestCase
=>
{
this
.
$emit
(
'
test-case-updated
'
,
updatedTestCase
);
})
.
finally
(()
=>
{
this
.
testCaseLabelsSelectInProgress
=
false
;
});
}
return
null
;
},
},
};
</
script
>
<
template
>
<div
class=
"test-case-sidebar-items"
>
<template
v-if=
"canEditTestCase"
>
<div
v-if=
"sidebarExpanded"
data-testid=
"todo"
class=
"block todo gl-display-flex"
>
<span
class=
"gl-flex-grow-1"
>
{{
__
(
'
To Do
'
)
}}
</span>
<gl-button
:loading=
"todoUpdateInProgress"
size=
"small"
@
click=
"handleTodoButtonClick"
>
{{
todoActionText
}}
</gl-button>
</div>
<div
v-else
class=
"block todo"
>
<button
v-gl-tooltip.viewport=
"
{ placement: 'left' }"
:title="todoActionText"
class="btn-blank sidebar-collapsed-icon"
@click="handleTodoButtonClick"
>
<gl-loading-icon
v-if=
"todoUpdateInProgress"
/>
<gl-icon
v-else
:name=
"todoIcon"
:class=
"
{ 'todo-undone': isTodoPending }" />
</button>
</div>
</
template
>
<labels-select
:allow-label-edit=
"canEditTestCase"
:allow-label-create=
"true"
:allow-multiselect=
"true"
:allow-scoped-labels=
"true"
:selected-labels=
"selectedLabels"
:labels-select-in-progress=
"testCaseLabelsSelectInProgress"
:labels-fetch-path=
"labelsFetchPath"
:labels-manage-path=
"labelsManagePath"
variant=
"sidebar"
class=
"block labels js-labels-block"
@
updateSelectedLabels=
"handleUpdateSelectedLabels"
@
onDropdownClose=
"handleLabelsDropdownClose"
@
toggleCollapse=
"handleLabelsCollapsedButtonClick"
>
{{ __('None') }}
</labels-select
>
</div>
</template>
ee/app/assets/javascripts/test_case_show/mixins/test_case_graphql.js
0 → 100644
View file @
cbc5129a
import
Api
from
'
~/api
'
;
import
createFlash
from
'
~/flash
'
;
import
{
s__
}
from
'
~/locale
'
;
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
'
;
export
default
{
apollo
:
{
testCase
:
{
query
:
projectTestCase
,
variables
()
{
return
{
projectPath
:
this
.
projectFullPath
,
testCaseId
:
this
.
testCaseId
,
};
},
update
(
data
)
{
return
data
.
project
?.
issue
;
},
result
()
{
this
.
testCaseLoading
=
false
;
},
error
(
error
)
{
this
.
testCaseLoadFailed
=
true
;
createFlash
({
message
:
s__
(
'
TestCases|Something went wrong while fetching test case.
'
),
captureError
:
true
,
error
,
});
throw
error
;
},
},
},
data
()
{
return
{
testCaseLoading
:
true
,
testCaseLoadFailed
:
false
,
testCaseTodoUpdateInProgress
:
false
,
};
},
methods
:
{
updateTestCase
({
variables
,
errorMessage
})
{
return
this
.
$apollo
.
mutate
({
mutation
:
updateTestCase
,
variables
:
{
updateTestCaseInput
:
{
projectPath
:
this
.
projectFullPath
,
iid
:
this
.
testCaseId
,
...
variables
,
},
},
})
.
then
(({
data
=
{}
})
=>
{
const
errors
=
data
.
updateIssue
?.
errors
;
if
(
errors
?.
length
)
{
throw
new
Error
(
`Error updating test case. Error message:
${
errors
[
0
].
message
}
`
);
}
return
data
.
updateIssue
?.
issue
;
})
.
catch
(
error
=>
{
createFlash
({
message
:
errorMessage
,
captureError
:
true
,
error
,
});
});
},
/**
* We're using Public REST API to add Test Case as a Todo since
* GraphQL mutation to do the same is unavailable as of now.
*/
addTestCaseAsTodo
()
{
this
.
testCaseTodoUpdateInProgress
=
true
;
return
Api
.
addProjectIssueAsTodo
(
this
.
projectFullPath
,
this
.
testCaseId
)
.
then
(()
=>
{
this
.
$apollo
.
queries
.
testCase
.
refetch
();
})
.
catch
(
error
=>
{
createFlash
({
message
:
s__
(
'
TestCases|Something went wrong while adding test case to Todo.
'
),
captureError
:
true
,
error
,
});
})
.
finally
(()
=>
{
this
.
testCaseTodoUpdateInProgress
=
false
;
});
},
markTestCaseTodoDone
()
{
this
.
testCaseTodoUpdateInProgress
=
true
;
return
this
.
$apollo
.
mutate
({
mutation
:
markTestCaseTodoDone
,
variables
:
{
todoMarkDoneInput
:
{
id
:
this
.
todo
.
id
,
},
},
})
.
then
(({
data
=
{}
})
=>
{
const
errors
=
data
.
todoMarkDone
?.
errors
;
if
(
errors
?.
length
)
{
throw
new
Error
(
`Error marking todo as done. Error message:
${
errors
[
0
].
message
}
`
);
}
this
.
$apollo
.
queries
.
testCase
.
refetch
();
})
.
catch
(
error
=>
{
createFlash
({
message
:
s__
(
'
TestCases|Something went wrong while marking test case todo as done.
'
),
captureError
:
true
,
error
,
});
})
.
finally
(()
=>
{
this
.
testCaseTodoUpdateInProgress
=
false
;
});
},
},
};
ee/app/assets/javascripts/test_case_show/queries/mark_test_case_todo_done.mutation.graphql
0 → 100644
View file @
cbc5129a
mutation
markTestCaseTodoDone
(
$todoMarkDoneInput
:
TodoMarkDoneInput
!)
{
todoMarkDone
(
input
:
$todoMarkDoneInput
)
{
errors
clientMutationId
todo
{
id
}
}
}
ee/app/assets/javascripts/test_case_show/queries/project_test_case.query.graphql
0 → 100644
View file @
cbc5129a
#import "./test_case.fragment.graphql"
query
projectTestCase
(
$projectPath
:
ID
!,
$testCaseId
:
String
)
{
project
(
fullPath
:
$projectPath
)
{
name
issue
(
iid
:
$testCaseId
)
{
...
TestCase
}
}
}
ee/app/assets/javascripts/test_case_show/queries/test_case.fragment.graphql
0 → 100644
View file @
cbc5129a
#import "~/graphql_shared/fragments/author.fragment.graphql"
#import "~/graphql_shared/fragments/label.fragment.graphql"
fragment
TestCase
on
Issue
{
id
title
titleHtml
description
descriptionHtml
state
createdAt
updatedAt
webUrl
blocked
confidential
author
{
...
Author
}
labels
{
nodes
{
...
Label
}
}
currentUserTodos
(
first
:
1
)
{
nodes
{
id
state
}
}
}
ee/app/assets/javascripts/test_case_show/queries/update_test_case.mutation.graphql
0 → 100644
View file @
cbc5129a
#import "./test_case.fragment.graphql"
mutation
updateTestCase
(
$updateTestCaseInput
:
UpdateIssueInput
!)
{
updateIssue
(
input
:
$updateTestCaseInput
)
{
clientMutationId
errors
issue
{
...
TestCase
}
}
}
ee/app/assets/javascripts/test_case_show/test_case_show_bundle.js
0 → 100644
View file @
cbc5129a
import
Vue
from
'
vue
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
createDefaultClient
from
'
~/lib/graphql
'
;
import
{
parseBoolean
}
from
'
~/lib/utils/common_utils
'
;
import
TestCaseShowApp
from
'
./components/test_case_show_root.vue
'
;
Vue
.
use
(
VueApollo
);
export
default
function
initTestCaseShow
({
mountPointSelector
})
{
const
el
=
document
.
querySelector
(
mountPointSelector
);
if
(
!
el
)
{
return
null
;
}
const
apolloProvider
=
new
VueApollo
({
defaultClient
:
createDefaultClient
(),
});
return
new
Vue
({
el
,
apolloProvider
,
provide
:
{
...
el
.
dataset
,
canEditTestCase
:
parseBoolean
(
el
.
dataset
.
canEditTestCase
),
},
render
:
createElement
=>
createElement
(
TestCaseShowApp
),
});
}
ee/app/views/projects/quality/test_cases/_show.html.haml
View file @
cbc5129a
...
@@ -5,5 +5,12 @@
...
@@ -5,5 +5,12 @@
-
page_title
"
#{
@issue
.
title
}
(
#{
@issue
.
to_reference
}
)"
,
_
(
'Test Cases'
)
-
page_title
"
#{
@issue
.
title
}
(
#{
@issue
.
to_reference
}
)"
,
_
(
'Test Cases'
)
-
page_description
@issue
.
description
-
page_description
@issue
.
description
-# haml-lint:disable InlineJavaScript
#js-issuable-app
{
data:
{
initial:
issuable_initial_data
(
@issue
).
to_json
,
%script
#js-issuable-app-initial-data
{
type:
"application/json"
}=
issuable_initial_data
(
@issue
).
to_json
can_edit_test_case:
can?
(
current_user
,
:admin_issue
,
@project
).
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
),
test_case_id:
@issue
.
iid
}
}
ee/spec/features/projects/quality/test_case_show_spec.rb
0 → 100644
View file @
cbc5129a
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
'Test Cases'
,
:js
do
let_it_be
(
:user
)
{
create
(
:user
)
}
let_it_be
(
:project
)
{
create
(
:project
,
:repository
)
}
let_it_be
(
:label_bug
)
{
create
(
:label
,
project:
project
,
title:
'bug'
)
}
let_it_be
(
:label_doc
)
{
create
(
:label
,
project:
project
,
title:
'documentation'
)
}
let_it_be
(
:test_case
)
{
create
(
:quality_test_case
,
project:
project
,
author:
user
,
description:
'Sample description'
,
created_at:
5
.
days
.
ago
,
updated_at:
2
.
days
.
ago
,
labels:
[
label_bug
])
}
before
do
project
.
add_developer
(
user
)
stub_licensed_features
(
quality_management:
true
)
sign_in
(
user
)
end
context
'test case page'
do
before
do
visit
project_issue_path
(
project
,
test_case
)
wait_for_all_requests
end
context
'header'
do
it
'shows status, created date and author'
do
page
.
within
(
'.test-case-container .detail-page-header-body'
)
do
expect
(
page
.
find
(
'.issuable-status-box'
)).
to
have_content
(
'Open'
)
expect
(
page
.
find
(
'.issuable-meta'
)).
to
have_content
(
'Opened 5 days ago'
)
expect
(
page
.
find
(
'.issuable-meta'
)).
to
have_link
(
user
.
name
)
end
end
it
'shows action buttons'
do
page
.
within
(
'.test-case-container .detail-page-header-actions'
)
do
expect
(
page
).
to
have_selector
(
'.dropdown'
,
visible:
false
)
expect
(
page
).
to
have_button
(
'Archive test case'
)
expect
(
page
).
to
have_link
(
'New test case'
,
href:
new_project_quality_test_case_path
(
project
))
end
end
it
'archives test case'
do
page
.
within
(
'.test-case-container'
)
do
click_button
'Archive test case'
wait_for_requests
expect
(
page
.
find
(
'.issuable-status-box'
)).
to
have_content
(
'Archived'
)
expect
(
page
).
to
have_button
(
'Reopen test case'
)
end
end
end
context
'body'
do
it
'shows title, description and edit button'
do
page
.
within
(
'.test-case-container .issuable-details'
)
do
expect
(
page
.
find
(
'.title'
)).
to
have_content
(
test_case
.
title
)
expect
(
page
.
find
(
'.description'
)).
to
have_content
(
test_case
.
description
)
expect
(
page
).
to
have_selector
(
'button.js-issuable-edit'
)
end
end
it
'makes title and description editable on edit click'
do
find
(
'.test-case-container .issuable-details .js-issuable-edit'
).
click
page
.
within
(
'.test-case-container .issuable-details form'
)
do
expect
(
page
.
find
(
'input#issuable-title'
).
value
).
to
eq
(
test_case
.
title
)
expect
(
page
.
find
(
'textarea#issuable-description'
).
value
).
to
eq
(
test_case
.
description
)
expect
(
page
).
to
have_button
(
'Save changes'
)
expect
(
page
).
to
have_button
(
'Cancel'
)
end
end
it
'update title and description'
do
title
=
'Updated title'
description
=
'Updated test case description.'
find
(
'.test-case-container .issuable-details .js-issuable-edit'
).
click
page
.
within
(
'.test-case-container .issuable-details form'
)
do
page
.
find
(
'input#issuable-title'
).
set
(
title
)
page
.
find
(
'textarea#issuable-description'
).
set
(
description
)
click_button
'Save changes'
end
wait_for_requests
page
.
within
(
'.test-case-container .issuable-details'
)
do
expect
(
page
.
find
(
'.title'
)).
to
have_content
(
title
)
expect
(
page
.
find
(
'.description'
)).
to
have_content
(
description
)
expect
(
page
.
find
(
'.edited-text'
)).
to
have_content
(
''
)
end
end
end
context
'sidebar'
do
it
'shows expand/collapse button'
do
page
.
within
(
'.test-case-container .right-sidebar'
)
do
expect
(
page
).
to
have_button
(
'Collapse sidebar'
)
end
end
context
'todo'
do
it
'shows todo status'
do
page
.
within
(
'.test-case-container .issuable-sidebar'
)
do
expect
(
page
.
find
(
'.block.todo'
)).
to
have_content
(
'To Do'
)
expect
(
page
).
to
have_button
(
'Add a to do'
)
end
end
it
'add test case as todo'
do
page
.
within
(
'.test-case-container .issuable-sidebar'
)
do
click_button
'Add a to do'
wait_for_all_requests
expect
(
page
).
to
have_button
(
'Mark as done'
)
end
end
it
'mark test case todo as done'
do
page
.
within
(
'.test-case-container .issuable-sidebar'
)
do
click_button
'Add a to do'
wait_for_all_requests
click_button
'Mark as done'
wait_for_all_requests
expect
(
page
).
to
have_button
(
'Add a to do'
)
end
end
end
context
'labels'
do
it
'shows assigned labels'
do
page
.
within
(
'.test-case-container .issuable-sidebar'
)
do
expect
(
page
).
to
have_selector
(
'.labels-select-wrapper'
)
expect
(
page
.
find
(
'.labels-select-wrapper .value'
)).
to
have_content
(
label_bug
.
title
)
end
end
it
'shows labels dropdown on edit click'
do
page
.
within
(
'.test-case-container .issuable-sidebar .labels-select-wrapper'
)
do
click_button
'Edit'
wait_for_requests
expect
(
page
.
find
(
'.js-labels-list .dropdown-content'
)).
to
have_selector
(
'li'
,
count:
2
)
expect
(
page
.
find
(
'.js-labels-list .dropdown-footer'
)).
to
have_selector
(
'li'
,
count:
2
)
end
end
it
'applies label using labels dropdown'
do
page
.
within
(
'.test-case-container .issuable-sidebar .labels-select-wrapper'
)
do
click_button
'Edit'
wait_for_requests
click_link
label_doc
.
title
click_button
'Edit'
wait_for_requests
expect
(
page
.
find
(
'.labels-select-wrapper .value'
)).
to
have_content
(
label_doc
.
title
)
end
end
end
end
end
end
ee/spec/frontend/test_case_show/components/test_case_show_root_spec.js
0 → 100644
View file @
cbc5129a
This diff is collapsed.
Click to expand it.
ee/spec/frontend/test_case_show/components/test_case_sidebar_spec.js
0 → 100644
View file @
cbc5129a
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
GlButton
,
GlIcon
}
from
'
@gitlab/ui
'
;
import
Mousetrap
from
'
mousetrap
'
;
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
{
mockProvide
,
mockTestCase
}
from
'
../mock_data
'
;
const
createComponent
=
({
sidebarExpanded
=
true
,
todo
=
mockCurrentUserTodo
,
selectedLabels
=
mockLabels
,
testCaseLoading
=
false
,
}
=
{})
=>
shallowMount
(
TestCaseSidebar
,
{
provide
:
{
...
mockProvide
,
},
propsData
:
{
sidebarExpanded
,
todo
,
selectedLabels
,
},
mocks
:
{
$apollo
:
{
queries
:
{
testCase
:
{
loading
:
testCaseLoading
,
},
},
},
},
});
describe
(
'
TestCaseSidebar
'
,
()
=>
{
let
mousetrapSpy
;
let
wrapper
;
beforeEach
(()
=>
{
mousetrapSpy
=
jest
.
spyOn
(
Mousetrap
,
'
bind
'
);
wrapper
=
createComponent
();
});
afterEach
(()
=>
{
wrapper
.
destroy
();
});
describe
(
'
computed
'
,
()
=>
{
describe
.
each
`
state | isTodoPending | todoActionText | todoIcon
${
'
pending
'
}
|
${
true
}
|
${
'
Mark as done
'
}
|
${
'
todo-done
'
}
${
'
done
'
}
|
${
false
}
|
${
'
Add a to do
'
}
|
${
'
todo-add
'
}
`
(
'
when `todo.state` is "$state"
'
,
({
state
,
isTodoPending
,
todoActionText
,
todoIcon
})
=>
{
beforeEach
(
async
()
=>
{
wrapper
.
setProps
({
todo
:
{
...
mockCurrentUserTodo
,
state
,
},
});
await
wrapper
.
vm
.
$nextTick
();
});
it
.
each
`
propName | propValue
${
'
isTodoPending
'
}
|
${
isTodoPending
}
${
'
todoActionText
'
}
|
${
todoActionText
}
${
'
todoIcon
'
}
|
${
todoIcon
}
`
(
'
computed prop `$propName` returns $propValue
'
,
({
propName
,
propValue
})
=>
{
expect
(
wrapper
.
vm
[
propName
]).
toBe
(
propValue
);
});
});
});
describe
(
'
mounted
'
,
()
=>
{
it
(
'
binds key-press listener for `l` on Mousetrap
'
,
()
=>
{
expect
(
mousetrapSpy
).
toHaveBeenCalledWith
(
'
l
'
,
wrapper
.
vm
.
handleLabelsCollapsedButtonClick
);
});
});
describe
(
'
methods
'
,
()
=>
{
describe
(
'
handleTodoButtonClick
'
,
()
=>
{
it
.
each
`
state | methodToCall
${
'
pending
'
}
|
${
'
markTestCaseTodoDone
'
}
${
'
done
'
}
|
${
'
addTestCaseAsTodo
'
}
`
(
'
calls `wrapper.vm.$methodToCall` when `todo.state` is "$state"
'
,
async
({
state
,
methodToCall
})
=>
{
jest
.
spyOn
(
wrapper
.
vm
,
methodToCall
).
mockImplementation
(
jest
.
fn
());
wrapper
.
setProps
({
todo
:
{
...
mockCurrentUserTodo
,
state
,
},
});
await
wrapper
.
vm
.
$nextTick
();
wrapper
.
vm
.
handleTodoButtonClick
();
expect
(
wrapper
.
vm
[
methodToCall
]).
toHaveBeenCalled
();
},
);
});
describe
(
'
toggleSidebar
'
,
()
=>
{
beforeEach
(()
=>
{
setFixtures
(
'
<button class="js-toggle-right-sidebar-button"></button>
'
);
});
it
(
'
dispatches click event on sidebar toggle button
'
,
()
=>
{
const
buttonEl
=
document
.
querySelector
(
'
.js-toggle-right-sidebar-button
'
);
jest
.
spyOn
(
buttonEl
,
'
dispatchEvent
'
);
wrapper
.
vm
.
toggleSidebar
();
expect
(
buttonEl
.
dispatchEvent
).
toHaveBeenCalledWith
(
expect
.
objectContaining
({
type
:
'
click
'
,
}),
);
});
});
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
'
,
()
=>
{
beforeEach
(()
=>
{
setFixtures
(
`
<div class="js-labels-block">
<button class="js-sidebar-dropdown-toggle"></button>
</div>
`
);
});
it
(
'
calls `toggleSidebar` method and sets `sidebarExpandedOnClick` to true when `sidebarExpanded` prop is false
'
,
async
()
=>
{
jest
.
spyOn
(
wrapper
.
vm
,
'
toggleSidebar
'
).
mockImplementation
(
jest
.
fn
());
wrapper
.
setProps
({
sidebarExpanded
:
false
,
});
await
wrapper
.
vm
.
$nextTick
();
wrapper
.
vm
.
handleLabelsCollapsedButtonClick
();
expect
(
wrapper
.
vm
.
toggleSidebar
).
toHaveBeenCalled
();
expect
(
wrapper
.
vm
.
sidebarExpandedOnClick
).
toBe
(
true
);
});
it
(
'
dispatches click event on label edit button
'
,
async
()
=>
{
const
buttonEl
=
document
.
querySelector
(
'
.js-sidebar-dropdown-toggle
'
);
jest
.
spyOn
(
wrapper
.
vm
,
'
toggleSidebar
'
).
mockImplementation
(
jest
.
fn
());
jest
.
spyOn
(
buttonEl
,
'
dispatchEvent
'
);
wrapper
.
setProps
({
sidebarExpanded
:
false
,
});
await
wrapper
.
vm
.
$nextTick
();
wrapper
.
vm
.
handleLabelsCollapsedButtonClick
();
await
wrapper
.
vm
.
$nextTick
();
expect
(
buttonEl
.
dispatchEvent
).
toHaveBeenCalledWith
(
expect
.
objectContaining
({
type
:
'
click
'
,
bubbles
:
true
,
cancelable
:
false
,
}),
);
});
});
describe
(
'
handleUpdateSelectedLabels
'
,
()
=>
{
const
updatedLabels
=
[
{
...
mockLabels
[
0
],
set
:
false
,
},
];
it
(
'
sets `testCaseLabelsSelectInProgress` to true when provided labels param includes any of the additions or removals
'
,
()
=>
{
jest
.
spyOn
(
wrapper
.
vm
,
'
updateTestCase
'
).
mockResolvedValue
(
mockTestCase
);
wrapper
.
vm
.
handleUpdateSelectedLabels
(
updatedLabels
);
expect
(
wrapper
.
vm
.
testCaseLabelsSelectInProgress
).
toBe
(
true
);
});
it
(
'
calls `updateTestCase` method with variables `addLabelIds` & `removeLabelIds` and erroMessage when provided labels param includes any of the additions or removals
'
,
()
=>
{
jest
.
spyOn
(
wrapper
.
vm
,
'
updateTestCase
'
).
mockResolvedValue
(
mockTestCase
);
wrapper
.
vm
.
handleUpdateSelectedLabels
(
updatedLabels
);
expect
(
wrapper
.
vm
.
updateTestCase
).
toHaveBeenCalledWith
({
variables
:
{
addLabelIds
:
[],
removeLabelIds
:
[
updatedLabels
[
0
].
id
],
},
errorMessage
:
'
Something went wrong while updating the test case labels.
'
,
});
});
it
(
'
emits "test-case-updated" event on component upon promise resolve
'
,
()
=>
{
jest
.
spyOn
(
wrapper
.
vm
,
'
updateTestCase
'
).
mockResolvedValue
(
mockTestCase
);
jest
.
spyOn
(
wrapper
.
vm
,
'
$emit
'
);
return
wrapper
.
vm
.
handleUpdateSelectedLabels
(
updatedLabels
).
then
(()
=>
{
expect
(
wrapper
.
vm
.
$emit
).
toHaveBeenCalledWith
(
'
test-case-updated
'
,
mockTestCase
);
});
});
it
(
'
sets `testCaseLabelsSelectInProgress` to false
'
,
()
=>
{
jest
.
spyOn
(
wrapper
.
vm
,
'
updateTestCase
'
).
mockResolvedValue
(
mockTestCase
);
return
wrapper
.
vm
.
handleUpdateSelectedLabels
(
updatedLabels
).
finally
(()
=>
{
expect
(
wrapper
.
vm
.
testCaseLabelsSelectInProgress
).
toBe
(
false
);
});
});
});
});
describe
(
'
template
'
,
()
=>
{
it
(
'
renders todo button
'
,
async
()
=>
{
let
todoEl
=
wrapper
.
find
(
'
[data-testid="todo"]
'
);
expect
(
todoEl
.
exists
()).
toBe
(
true
);
expect
(
todoEl
.
text
()).
toContain
(
'
To Do
'
);
expect
(
todoEl
.
find
(
GlButton
).
exists
()).
toBe
(
true
);
expect
(
todoEl
.
find
(
GlButton
).
text
()).
toBe
(
'
Add a to do
'
);
wrapper
.
setProps
({
sidebarExpanded
:
false
,
});
await
wrapper
.
vm
.
$nextTick
();
todoEl
=
wrapper
.
find
(
'
button
'
);
expect
(
todoEl
.
exists
()).
toBe
(
true
);
expect
(
todoEl
.
attributes
(
'
title
'
)).
toBe
(
'
Add a to do
'
);
expect
(
todoEl
.
find
(
GlIcon
).
exists
()).
toBe
(
true
);
});
it
(
'
renders label-select
'
,
async
()
=>
{
const
{
selectedLabels
,
testCaseLabelsSelectInProgress
}
=
wrapper
.
vm
;
const
{
canEditTestCase
,
labelsFetchPath
,
labelsManagePath
}
=
mockProvide
;
const
labelSelectEl
=
wrapper
.
find
(
LabelsSelect
);
expect
(
labelSelectEl
.
exists
()).
toBe
(
true
);
expect
(
labelSelectEl
.
props
()).
toMatchObject
({
selectedLabels
,
labelsFetchPath
,
labelsManagePath
,
allowLabelCreate
:
true
,
allowMultiselect
:
true
,
variant
:
'
sidebar
'
,
allowLabelEdit
:
canEditTestCase
,
labelsSelectInProgress
:
testCaseLabelsSelectInProgress
,
});
expect
(
labelSelectEl
.
text
()).
toBe
(
'
None
'
);
});
});
});
ee/spec/frontend/test_case_show/mixins/test_case_graphql_spec.js
0 → 100644
View file @
cbc5129a
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
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
createFlash
from
'
~/flash
'
;
import
Api
from
'
~/api
'
;
import
{
mockProvide
,
mockTestCase
}
from
'
../mock_data
'
;
jest
.
mock
(
'
~/flash
'
);
const
createComponent
=
({
testCase
,
testCaseQueryLoading
=
false
}
=
{})
=>
shallowMount
(
TestCaseShowRoot
,
{
provide
:
{
...
mockProvide
,
},
mocks
:
{
$apollo
:
{
queries
:
{
testCase
:
{
loading
:
testCaseQueryLoading
,
refetch
:
jest
.
fn
(),
},
},
mutate
:
jest
.
fn
(),
},
},
data
()
{
return
{
testCaseLoading
:
testCaseQueryLoading
,
testCase
:
testCaseQueryLoading
?
{}
:
{
...
mockTestCase
,
...
testCase
,
},
};
},
});
describe
(
'
TestCaseGraphQL Mixin
'
,
()
=>
{
let
wrapper
;
beforeEach
(()
=>
{
wrapper
=
createComponent
();
});
afterEach
(()
=>
{
wrapper
.
destroy
();
});
describe
(
'
updateTestCase
'
,
()
=>
{
it
(
'
calls `$apollo.mutate` with updateTestCase mutation and updateTestCaseInput variables
'
,
()
=>
{
jest
.
spyOn
(
wrapper
.
vm
.
$apollo
,
'
mutate
'
).
mockResolvedValue
({
data
:
{
updateIssue
:
{
errors
:
[],
issue
:
mockTestCase
,
},
},
});
wrapper
.
vm
.
updateTestCase
({
variables
:
{
title
:
'
Foo
'
,
},
errorMessage
:
'
Something went wrong
'
,
});
expect
(
wrapper
.
vm
.
$apollo
.
mutate
).
toHaveBeenCalledWith
({
mutation
:
updateTestCase
,
variables
:
{
updateTestCaseInput
:
{
projectPath
:
mockProvide
.
projectFullPath
,
iid
:
mockProvide
.
testCaseId
,
title
:
'
Foo
'
,
},
},
});
});
it
(
'
calls `createFlash` with errorMessage on promise reject
'
,
()
=>
{
jest
.
spyOn
(
wrapper
.
vm
.
$apollo
,
'
mutate
'
).
mockRejectedValue
({});
return
wrapper
.
vm
.
updateTestCase
({
variables
:
{
title
:
'
Foo
'
,
},
errorMessage
:
'
Something went wrong
'
,
})
.
then
(()
=>
{
expect
(
createFlash
).
toHaveBeenCalledWith
({
message
:
'
Something went wrong
'
,
captureError
:
true
,
error
:
expect
.
any
(
Object
),
});
});
});
});
describe
(
'
addTestCaseAsTodo
'
,
()
=>
{
it
(
'
sets `testCaseTodoUpdateInProgress` to true
'
,
()
=>
{
jest
.
spyOn
(
Api
,
'
addProjectIssueAsTodo
'
).
mockResolvedValue
({});
wrapper
.
vm
.
addTestCaseAsTodo
();
expect
(
wrapper
.
vm
.
testCaseTodoUpdateInProgress
).
toBe
(
true
);
});
it
(
'
calls `Api.addProjectIssueAsTodo` method with params `projectFullPath` and `testCaseId`
'
,
()
=>
{
jest
.
spyOn
(
Api
,
'
addProjectIssueAsTodo
'
).
mockResolvedValue
({});
wrapper
.
vm
.
addTestCaseAsTodo
();
expect
(
Api
.
addProjectIssueAsTodo
).
toHaveBeenCalledWith
(
mockProvide
.
projectFullPath
,
mockProvide
.
testCaseId
,
);
});
it
(
'
calls `$apollo.queries.testCase.refetch` method on request promise resolve
'
,
()
=>
{
jest
.
spyOn
(
Api
,
'
addProjectIssueAsTodo
'
).
mockResolvedValue
({});
jest
.
spyOn
(
wrapper
.
vm
.
$apollo
.
queries
.
testCase
,
'
refetch
'
);
return
wrapper
.
vm
.
addTestCaseAsTodo
().
then
(()
=>
{
expect
(
wrapper
.
vm
.
$apollo
.
queries
.
testCase
.
refetch
).
toHaveBeenCalled
();
});
});
it
(
'
calls `createFlash` method on request promise reject
'
,
()
=>
{
jest
.
spyOn
(
Api
,
'
addProjectIssueAsTodo
'
).
mockRejectedValue
({});
return
wrapper
.
vm
.
addTestCaseAsTodo
().
then
(()
=>
{
expect
(
createFlash
).
toHaveBeenCalledWith
({
message
:
'
Something went wrong while adding test case to Todo.
'
,
captureError
:
true
,
error
:
expect
.
any
(
Object
),
});
});
});
it
(
'
sets `testCaseTodoUpdateInProgress` to false on request promise resolve or reject
'
,
()
=>
{
jest
.
spyOn
(
Api
,
'
addProjectIssueAsTodo
'
).
mockRejectedValue
({});
return
wrapper
.
vm
.
addTestCaseAsTodo
().
finally
(()
=>
{
expect
(
wrapper
.
vm
.
testCaseTodoUpdateInProgress
).
toBe
(
false
);
});
});
});
describe
(
'
markTestCaseTodoDone
'
,
()
=>
{
const
todoResolvedMutation
=
{
data
:
{
todoMarkDone
:
{
errors
:
[],
},
},
};
it
(
'
sets `testCaseTodoUpdateInProgress` to true
'
,
()
=>
{
jest
.
spyOn
(
wrapper
.
vm
.
$apollo
,
'
mutate
'
).
mockResolvedValue
(
todoResolvedMutation
);
wrapper
.
vm
.
markTestCaseTodoDone
();
expect
(
wrapper
.
vm
.
testCaseTodoUpdateInProgress
).
toBe
(
true
);
});
it
(
'
calls `$apollo.mutate` with markTestCaseTodoDone mutation and todoMarkDoneInput variables
'
,
()
=>
{
jest
.
spyOn
(
wrapper
.
vm
.
$apollo
,
'
mutate
'
).
mockResolvedValue
(
todoResolvedMutation
);
wrapper
.
vm
.
markTestCaseTodoDone
();
expect
(
wrapper
.
vm
.
$apollo
.
mutate
).
toHaveBeenCalledWith
({
mutation
:
markTestCaseTodoDone
,
variables
:
{
todoMarkDoneInput
:
{
id
:
mockCurrentUserTodo
.
id
,
},
},
});
});
it
(
'
calls `$apollo.queries.testCase.refetch` on mutation promise resolve
'
,
()
=>
{
jest
.
spyOn
(
wrapper
.
vm
.
$apollo
,
'
mutate
'
).
mockResolvedValue
(
todoResolvedMutation
);
jest
.
spyOn
(
wrapper
.
vm
.
$apollo
.
queries
.
testCase
,
'
refetch
'
);
return
wrapper
.
vm
.
markTestCaseTodoDone
().
then
(()
=>
{
expect
(
wrapper
.
vm
.
$apollo
.
queries
.
testCase
.
refetch
).
toHaveBeenCalled
();
});
});
it
(
'
calls `createFlash` with errorMessage on mutation promise reject
'
,
()
=>
{
jest
.
spyOn
(
wrapper
.
vm
.
$apollo
,
'
mutate
'
).
mockRejectedValue
({});
return
wrapper
.
vm
.
markTestCaseTodoDone
().
then
(()
=>
{
expect
(
createFlash
).
toHaveBeenCalledWith
({
message
:
'
Something went wrong while marking test case todo as done.
'
,
captureError
:
true
,
error
:
expect
.
any
(
Object
),
});
});
});
it
(
'
sets `testCaseTodoUpdateInProgress` to false on mutation promise resolve or reject
'
,
()
=>
{
jest
.
spyOn
(
wrapper
.
vm
.
$apollo
,
'
mutate
'
).
mockResolvedValue
(
todoResolvedMutation
);
return
wrapper
.
vm
.
markTestCaseTodoDone
().
finally
(()
=>
{
expect
(
wrapper
.
vm
.
testCaseTodoUpdateInProgress
).
toBe
(
false
);
});
});
});
});
ee/spec/frontend/test_case_show/mock_data.js
0 → 100644
View file @
cbc5129a
import
{
mockIssuable
,
mockCurrentUserTodo
}
from
'
jest/issuable_list/mock_data
'
;
export
const
mockTestCase
=
{
...
mockIssuable
,
currentUserTodos
:
{
nodes
:
[
mockCurrentUserTodo
],
},
};
export
const
mockProvide
=
{
projectFullPath
:
'
gitlab-org/gitlab-test
'
,
testCaseNewPath
:
'
/gitlab-org/gitlab-test/-/quality/test_cases/new
'
,
testCaseId
:
mockIssuable
.
iid
,
canEditTestCase
:
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
'
,
};
locale/gitlab.pot
View file @
cbc5129a
...
@@ -3425,6 +3425,9 @@ msgstr ""
...
@@ -3425,6 +3425,9 @@ msgstr ""
msgid "Archive project"
msgid "Archive project"
msgstr ""
msgstr ""
msgid "Archive test case"
msgstr ""
msgid "Archived"
msgid "Archived"
msgstr ""
msgstr ""
...
@@ -22070,6 +22073,9 @@ msgstr ""
...
@@ -22070,6 +22073,9 @@ msgstr ""
msgid "Reopen milestone"
msgid "Reopen milestone"
msgstr ""
msgstr ""
msgid "Reopen test case"
msgstr ""
msgid "Reopen this %{quick_action_target}"
msgid "Reopen this %{quick_action_target}"
msgstr ""
msgstr ""
...
@@ -25897,15 +25903,30 @@ msgstr ""
...
@@ -25897,15 +25903,30 @@ msgstr ""
msgid "TestCases|Search test cases"
msgid "TestCases|Search test cases"
msgstr ""
msgstr ""
msgid "TestCases|Something went wrong while adding test case to Todo."
msgstr ""
msgid "TestCases|Something went wrong while creating a test case."
msgid "TestCases|Something went wrong while creating a test case."
msgstr ""
msgstr ""
msgid "TestCases|Something went wrong while fetching count of test cases."
msgid "TestCases|Something went wrong while fetching count of test cases."
msgstr ""
msgstr ""
msgid "TestCases|Something went wrong while fetching test case."
msgstr ""
msgid "TestCases|Something went wrong while fetching test cases list."
msgid "TestCases|Something went wrong while fetching test cases list."
msgstr ""
msgstr ""
msgid "TestCases|Something went wrong while marking test case todo as done."
msgstr ""
msgid "TestCases|Something went wrong while updating the test case labels."
msgstr ""
msgid "TestCases|Something went wrong while updating the test case."
msgstr ""
msgid "TestCases|Submit test case"
msgid "TestCases|Submit test case"
msgstr ""
msgstr ""
...
...
spec/frontend/api_spec.js
View file @
cbc5129a
...
@@ -421,6 +421,25 @@ describe('Api', () => {
...
@@ -421,6 +421,25 @@ describe('Api', () => {
});
});
});
});
describe
(
'
addProjectIssueAsTodo
'
,
()
=>
{
it
(
'
adds issue ID as a todo
'
,
()
=>
{
const
projectId
=
1
;
const
issueIid
=
11
;
const
expectedUrl
=
`
${
dummyUrlRoot
}
/api/
${
dummyApiVersion
}
/projects/1/issues/11/todo`
;
mock
.
onPost
(
expectedUrl
).
reply
(
200
,
{
id
:
112
,
project
:
{
id
:
1
,
},
});
return
Api
.
addProjectIssueAsTodo
(
projectId
,
issueIid
).
then
(({
data
})
=>
{
expect
(
data
.
id
).
toBe
(
112
);
expect
(
data
.
project
.
id
).
toBe
(
projectId
);
});
});
});
describe
(
'
newLabel
'
,
()
=>
{
describe
(
'
newLabel
'
,
()
=>
{
it
(
'
creates a new label
'
,
done
=>
{
it
(
'
creates a new label
'
,
done
=>
{
const
namespace
=
'
some namespace
'
;
const
namespace
=
'
some namespace
'
;
...
...
spec/frontend/issuable_list/mock_data.js
View file @
cbc5129a
...
@@ -30,13 +30,23 @@ export const mockScopedLabel = {
...
@@ -30,13 +30,23 @@ export const mockScopedLabel = {
export
const
mockLabels
=
[
mockRegularLabel
,
mockScopedLabel
];
export
const
mockLabels
=
[
mockRegularLabel
,
mockScopedLabel
];
export
const
mockCurrentUserTodo
=
{
id
:
'
gid://gitlab/Todo/489
'
,
state
:
'
done
'
,
};
export
const
mockIssuable
=
{
export
const
mockIssuable
=
{
iid
:
'
30
'
,
iid
:
'
30
'
,
title
:
'
Dismiss Cipher with no integrity
'
,
title
:
'
Dismiss Cipher with no integrity
'
,
description
:
null
,
titleHtml
:
'
Dismiss Cipher with no integrity
'
,
description
:
'
fortitudinis _fomentis_ dolor mitigari solet.
'
,
descriptionHtml
:
'
fortitudinis <i>fomentis</i> dolor mitigari solet.
'
,
state
:
'
opened
'
,
createdAt
:
'
2020-06-29T13:52:56Z
'
,
createdAt
:
'
2020-06-29T13:52:56Z
'
,
updatedAt
:
'
2020-09-10T11:41:13Z
'
,
updatedAt
:
'
2020-09-10T11:41:13Z
'
,
webUrl
:
'
http://0.0.0.0:3000/gitlab-org/gitlab-shell/-/issues/30
'
,
webUrl
:
'
http://0.0.0.0:3000/gitlab-org/gitlab-shell/-/issues/30
'
,
blocked
:
false
,
confidential
:
false
,
author
:
mockAuthor
,
author
:
mockAuthor
,
labels
:
{
labels
:
{
nodes
:
mockLabels
,
nodes
:
mockLabels
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment