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
ec0d8e14
Commit
ec0d8e14
authored
Sep 29, 2021
by
Coung Ngo
Committed by
Alex Pooley
Sep 29, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add new issue split dropdown to group issues list refactor
parent
dab58c88
Changes
15
Show whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
358 additions
and
16 deletions
+358
-16
app/assets/javascripts/issues_list/components/issues_list_app.vue
...ts/javascripts/issues_list/components/issues_list_app.vue
+10
-0
app/assets/javascripts/issues_list/components/new_issue_dropdown.vue
...javascripts/issues_list/components/new_issue_dropdown.vue
+124
-0
app/assets/javascripts/issues_list/index.js
app/assets/javascripts/issues_list/index.js
+2
-0
app/assets/javascripts/issues_list/queries/search_projects.query.graphql
...scripts/issues_list/queries/search_projects.query.graphql
+12
-0
app/assets/javascripts/lib/utils/url_utility.js
app/assets/javascripts/lib/utils/url_utility.js
+2
-0
app/assets/stylesheets/framework/blocks.scss
app/assets/stylesheets/framework/blocks.scss
+1
-1
app/helpers/issues_helper.rb
app/helpers/issues_helper.rb
+3
-2
app/views/groups/issues.html.haml
app/views/groups/issues.html.haml
+1
-1
ee/app/helpers/ee/issues_helper.rb
ee/app/helpers/ee/issues_helper.rb
+1
-1
ee/spec/helpers/ee/issues_helper_spec.rb
ee/spec/helpers/ee/issues_helper_spec.rb
+3
-2
locale/gitlab.pot
locale/gitlab.pot
+9
-0
spec/frontend/issues_list/components/issues_list_app_spec.js
spec/frontend/issues_list/components/issues_list_app_spec.js
+22
-8
spec/frontend/issues_list/components/new_issue_dropdown_spec.js
...rontend/issues_list/components/new_issue_dropdown_spec.js
+131
-0
spec/frontend/issues_list/mock_data.js
spec/frontend/issues_list/mock_data.js
+34
-0
spec/helpers/issues_helper_spec.rb
spec/helpers/issues_helper_spec.rb
+3
-1
No files found.
app/assets/javascripts/issues_list/components/issues_list_app.vue
View file @
ec0d8e14
...
@@ -82,6 +82,7 @@ import searchLabelsQuery from '../queries/search_labels.query.graphql';
...
@@ -82,6 +82,7 @@ import searchLabelsQuery from '../queries/search_labels.query.graphql';
import
searchMilestonesQuery
from
'
../queries/search_milestones.query.graphql
'
;
import
searchMilestonesQuery
from
'
../queries/search_milestones.query.graphql
'
;
import
searchUsersQuery
from
'
../queries/search_users.query.graphql
'
;
import
searchUsersQuery
from
'
../queries/search_users.query.graphql
'
;
import
IssueCardTimeInfo
from
'
./issue_card_time_info.vue
'
;
import
IssueCardTimeInfo
from
'
./issue_card_time_info.vue
'
;
import
NewIssueDropdown
from
'
./new_issue_dropdown.vue
'
;
export
default
{
export
default
{
i18n
,
i18n
,
...
@@ -96,6 +97,7 @@ export default {
...
@@ -96,6 +97,7 @@ export default {
IssuableByEmail
,
IssuableByEmail
,
IssuableList
,
IssuableList
,
IssueCardTimeInfo
,
IssueCardTimeInfo
,
NewIssueDropdown
,
BlockingIssuesCount
:
()
=>
import
(
'
ee_component/issues/components/blocking_issues_count.vue
'
),
BlockingIssuesCount
:
()
=>
import
(
'
ee_component/issues/components/blocking_issues_count.vue
'
),
},
},
directives
:
{
directives
:
{
...
@@ -126,6 +128,9 @@ export default {
...
@@ -126,6 +128,9 @@ export default {
hasAnyIssues
:
{
hasAnyIssues
:
{
default
:
false
,
default
:
false
,
},
},
hasAnyProjects
:
{
default
:
false
,
},
hasBlockedIssuesFeature
:
{
hasBlockedIssuesFeature
:
{
default
:
false
,
default
:
false
,
},
},
...
@@ -253,6 +258,9 @@ export default {
...
@@ -253,6 +258,9 @@ export default {
showCsvButtons
()
{
showCsvButtons
()
{
return
this
.
isProject
&&
this
.
isSignedIn
;
return
this
.
isProject
&&
this
.
isSignedIn
;
},
},
showNewIssueDropdown
()
{
return
!
this
.
isProject
&&
this
.
hasAnyProjects
;
},
apiFilterParams
()
{
apiFilterParams
()
{
return
convertToApiParams
(
this
.
filterTokens
);
return
convertToApiParams
(
this
.
filterTokens
);
},
},
...
@@ -662,6 +670,7 @@ export default {
...
@@ -662,6 +670,7 @@ export default {
<gl-button
v-if=
"showNewIssueLink"
:href=
"newIssuePath"
variant=
"confirm"
>
<gl-button
v-if=
"showNewIssueLink"
:href=
"newIssuePath"
variant=
"confirm"
>
{{
$options
.
i18n
.
newIssueLabel
}}
{{
$options
.
i18n
.
newIssueLabel
}}
</gl-button>
</gl-button>
<new-issue-dropdown
v-if=
"showNewIssueDropdown"
/>
</
template
>
</
template
>
<
template
#timeframe=
"{ issuable = {} }"
>
<
template
#timeframe=
"{ issuable = {} }"
>
...
@@ -765,6 +774,7 @@ export default {
...
@@ -765,6 +774,7 @@ export default {
:export-csv-path=
"exportCsvPathWithQuery"
:export-csv-path=
"exportCsvPathWithQuery"
:issuable-count=
"currentTabCount"
:issuable-count=
"currentTabCount"
/>
/>
<new-issue-dropdown
v-if=
"showNewIssueDropdown"
/>
</
template
>
</
template
>
</gl-empty-state>
</gl-empty-state>
<hr
/>
<hr
/>
...
...
app/assets/javascripts/issues_list/components/new_issue_dropdown.vue
0 → 100644
View file @
ec0d8e14
<
script
>
import
{
GlDropdown
,
GlDropdownItem
,
GlDropdownText
,
GlLoadingIcon
,
GlSearchBoxByType
,
}
from
'
@gitlab/ui
'
;
import
createFlash
from
'
~/flash
'
;
import
searchProjectsQuery
from
'
~/issues_list/queries/search_projects.query.graphql
'
;
import
{
DASH_SCOPE
,
joinPaths
}
from
'
~/lib/utils/url_utility
'
;
import
{
__
,
sprintf
}
from
'
~/locale
'
;
import
{
DEBOUNCE_DELAY
}
from
'
~/vue_shared/components/filtered_search_bar/constants
'
;
export
default
{
i18n
:
{
defaultDropdownText
:
__
(
'
Select project to create issue
'
),
noMatchesFound
:
__
(
'
No matches found
'
),
toggleButtonLabel
:
__
(
'
Toggle project select
'
),
},
components
:
{
GlDropdown
,
GlDropdownItem
,
GlDropdownText
,
GlLoadingIcon
,
GlSearchBoxByType
,
},
inject
:
[
'
fullPath
'
],
data
()
{
return
{
projects
:
[],
search
:
''
,
selectedProject
:
{},
shouldSkipQuery
:
true
,
};
},
apollo
:
{
projects
:
{
query
:
searchProjectsQuery
,
variables
()
{
return
{
fullPath
:
this
.
fullPath
,
search
:
this
.
search
,
};
},
update
:
({
group
})
=>
group
.
projects
.
nodes
??
[],
error
(
error
)
{
createFlash
({
message
:
__
(
'
An error occurred while loading projects.
'
),
captureError
:
true
,
error
,
});
},
skip
()
{
return
this
.
shouldSkipQuery
;
},
debounce
:
DEBOUNCE_DELAY
,
},
},
computed
:
{
dropdownHref
()
{
return
this
.
hasSelectedProject
?
joinPaths
(
this
.
selectedProject
.
webUrl
,
DASH_SCOPE
,
'
issues/new
'
)
:
undefined
;
},
dropdownText
()
{
return
this
.
hasSelectedProject
?
sprintf
(
__
(
'
New issue in %{project}
'
),
{
project
:
this
.
selectedProject
.
name
})
:
this
.
$options
.
i18n
.
defaultDropdownText
;
},
hasSelectedProject
()
{
return
this
.
selectedProject
.
id
;
},
showNoSearchResultsText
()
{
return
!
this
.
projects
.
length
&&
this
.
search
;
},
},
methods
:
{
handleDropdownClick
()
{
if
(
!
this
.
dropdownHref
)
{
this
.
$refs
.
dropdown
.
show
();
}
},
handleDropdownShown
()
{
if
(
this
.
shouldSkipQuery
)
{
this
.
shouldSkipQuery
=
false
;
}
this
.
$refs
.
search
.
focusInput
();
},
selectProject
(
project
)
{
this
.
selectedProject
=
project
;
},
},
};
</
script
>
<
template
>
<gl-dropdown
ref=
"dropdown"
right
split
:split-href=
"dropdownHref"
:text=
"dropdownText"
:toggle-text=
"$options.i18n.toggleButtonLabel"
variant=
"confirm"
@
click=
"handleDropdownClick"
@
shown=
"handleDropdownShown"
>
<gl-search-box-by-type
ref=
"search"
v-model.trim=
"search"
/>
<gl-loading-icon
v-if=
"$apollo.queries.projects.loading"
/>
<template
v-else
>
<gl-dropdown-item
v-for=
"project of projects"
:key=
"project.id"
@
click=
"selectProject(project)"
>
{{
project
.
nameWithNamespace
}}
</gl-dropdown-item>
<gl-dropdown-text
v-if=
"showNoSearchResultsText"
>
{{
$options
.
i18n
.
noMatchesFound
}}
</gl-dropdown-text>
</
template
>
</gl-dropdown>
</template>
app/assets/javascripts/issues_list/index.js
View file @
ec0d8e14
...
@@ -121,6 +121,7 @@ export function mountIssuesListApp() {
...
@@ -121,6 +121,7 @@ export function mountIssuesListApp() {
fullPath
,
fullPath
,
groupEpicsPath
,
groupEpicsPath
,
hasAnyIssues
,
hasAnyIssues
,
hasAnyProjects
,
hasBlockedIssuesFeature
,
hasBlockedIssuesFeature
,
hasIssuableHealthStatusFeature
,
hasIssuableHealthStatusFeature
,
hasIssueWeightsFeature
,
hasIssueWeightsFeature
,
...
@@ -153,6 +154,7 @@ export function mountIssuesListApp() {
...
@@ -153,6 +154,7 @@ export function mountIssuesListApp() {
fullPath
,
fullPath
,
groupEpicsPath
,
groupEpicsPath
,
hasAnyIssues
:
parseBoolean
(
hasAnyIssues
),
hasAnyIssues
:
parseBoolean
(
hasAnyIssues
),
hasAnyProjects
:
parseBoolean
(
hasAnyProjects
),
hasBlockedIssuesFeature
:
parseBoolean
(
hasBlockedIssuesFeature
),
hasBlockedIssuesFeature
:
parseBoolean
(
hasBlockedIssuesFeature
),
hasIssuableHealthStatusFeature
:
parseBoolean
(
hasIssuableHealthStatusFeature
),
hasIssuableHealthStatusFeature
:
parseBoolean
(
hasIssuableHealthStatusFeature
),
hasIssueWeightsFeature
:
parseBoolean
(
hasIssueWeightsFeature
),
hasIssueWeightsFeature
:
parseBoolean
(
hasIssueWeightsFeature
),
...
...
app/assets/javascripts/issues_list/queries/search_projects.query.graphql
0 → 100644
View file @
ec0d8e14
query
searchProjects
(
$fullPath
:
ID
!,
$search
:
String
)
{
group
(
fullPath
:
$fullPath
)
{
projects
(
search
:
$search
,
includeSubgroups
:
true
)
{
nodes
{
id
name
nameWithNamespace
webUrl
}
}
}
}
app/assets/javascripts/lib/utils/url_utility.js
View file @
ec0d8e14
export
const
DASH_SCOPE
=
'
-
'
;
const
PATH_SEPARATOR
=
'
/
'
;
const
PATH_SEPARATOR
=
'
/
'
;
const
PATH_SEPARATOR_LEADING_REGEX
=
new
RegExp
(
`^
${
PATH_SEPARATOR
}
+`
);
const
PATH_SEPARATOR_LEADING_REGEX
=
new
RegExp
(
`^
${
PATH_SEPARATOR
}
+`
);
const
PATH_SEPARATOR_ENDING_REGEX
=
new
RegExp
(
`
${
PATH_SEPARATOR
}
+$`
);
const
PATH_SEPARATOR_ENDING_REGEX
=
new
RegExp
(
`
${
PATH_SEPARATOR
}
+$`
);
...
...
app/assets/stylesheets/framework/blocks.scss
View file @
ec0d8e14
...
@@ -322,7 +322,7 @@
...
@@ -322,7 +322,7 @@
display
:
inline-block
;
display
:
inline-block
;
}
}
.btn
{
.btn
:not
(
.split-content-button
)
:not
(
.dropdown-toggle-split
)
{
margin
:
$gl-padding-8
$gl-padding-4
;
margin
:
$gl-padding-8
$gl-padding-4
;
@include
media-breakpoint-down
(
xs
)
{
@include
media-breakpoint-down
(
xs
)
{
...
...
app/helpers/issues_helper.rb
View file @
ec0d8e14
...
@@ -238,9 +238,10 @@ module IssuesHelper
...
@@ -238,9 +238,10 @@ module IssuesHelper
)
)
end
end
def
group_issues_list_data
(
group
,
current_user
,
issues
)
def
group_issues_list_data
(
group
,
current_user
,
issues
,
projects
)
common_issues_list_data
(
group
,
current_user
).
merge
(
common_issues_list_data
(
group
,
current_user
).
merge
(
has_any_issues:
issues
.
to_a
.
any?
.
to_s
has_any_issues:
issues
.
to_a
.
any?
.
to_s
,
has_any_projects:
any_projects?
(
projects
).
to_s
)
)
end
end
...
...
app/views/groups/issues.html.haml
View file @
ec0d8e14
...
@@ -6,7 +6,7 @@
...
@@ -6,7 +6,7 @@
=
auto_discovery_link_tag
(
:atom
,
safe_params
.
merge
(
rss_url_options
).
to_h
,
title:
"
#{
@group
.
name
}
issues"
)
=
auto_discovery_link_tag
(
:atom
,
safe_params
.
merge
(
rss_url_options
).
to_h
,
title:
"
#{
@group
.
name
}
issues"
)
-
if
Feature
.
enabled?
(
:vue_issues_list
,
@group
,
default_enabled: :yaml
)
-
if
Feature
.
enabled?
(
:vue_issues_list
,
@group
,
default_enabled: :yaml
)
.js-issues-list
{
data:
group_issues_list_data
(
@group
,
current_user
,
@issues
)
}
.js-issues-list
{
data:
group_issues_list_data
(
@group
,
current_user
,
@issues
,
@projects
)
}
-
if
@can_bulk_update
-
if
@can_bulk_update
=
render_if_exists
'shared/issuable/group_bulk_update_sidebar'
,
group:
@group
,
type: :issues
=
render_if_exists
'shared/issuable/group_bulk_update_sidebar'
,
group:
@group
,
type: :issues
-
else
-
else
...
...
ee/app/helpers/ee/issues_helper.rb
View file @
ec0d8e14
...
@@ -63,7 +63,7 @@ module EE
...
@@ -63,7 +63,7 @@ module EE
end
end
override
:group_issues_list_data
override
:group_issues_list_data
def
group_issues_list_data
(
group
,
current_user
,
issues
)
def
group_issues_list_data
(
group
,
current_user
,
issues
,
projects
)
super
.
tap
do
|
data
|
super
.
tap
do
|
data
|
data
[
:can_bulk_update
]
=
(
can?
(
current_user
,
:admin_issue
,
group
)
&&
group
.
feature_available?
(
:group_bulk_edit
)).
to_s
data
[
:can_bulk_update
]
=
(
can?
(
current_user
,
:admin_issue
,
group
)
&&
group
.
feature_available?
(
:group_bulk_edit
)).
to_s
...
...
ee/spec/helpers/ee/issues_helper_spec.rb
View file @
ec0d8e14
...
@@ -187,6 +187,7 @@ RSpec.describe EE::IssuesHelper do
...
@@ -187,6 +187,7 @@ RSpec.describe EE::IssuesHelper do
describe
'#group_issues_list_data'
do
describe
'#group_issues_list_data'
do
let
(
:current_user
)
{
double
.
as_null_object
}
let
(
:current_user
)
{
double
.
as_null_object
}
let
(
:issues
)
{
[]
}
let
(
:issues
)
{
[]
}
let
(
:projects
)
{
[]
}
before
do
before
do
allow
(
helper
).
to
receive
(
:current_user
).
and_return
(
current_user
)
allow
(
helper
).
to
receive
(
:current_user
).
and_return
(
current_user
)
...
@@ -210,7 +211,7 @@ RSpec.describe EE::IssuesHelper do
...
@@ -210,7 +211,7 @@ RSpec.describe EE::IssuesHelper do
group_epics_path:
group_epics_path
(
project
.
group
,
format: :json
)
group_epics_path:
group_epics_path
(
project
.
group
,
format: :json
)
}
}
expect
(
helper
.
group_issues_list_data
(
group
,
current_user
,
issues
)).
to
include
(
expected
)
expect
(
helper
.
group_issues_list_data
(
group
,
current_user
,
issues
,
projects
)).
to
include
(
expected
)
end
end
end
end
...
@@ -229,7 +230,7 @@ RSpec.describe EE::IssuesHelper do
...
@@ -229,7 +230,7 @@ RSpec.describe EE::IssuesHelper do
has_multiple_issue_assignees_feature:
'false'
has_multiple_issue_assignees_feature:
'false'
}
}
result
=
helper
.
group_issues_list_data
(
group
,
current_user
,
issues
)
result
=
helper
.
group_issues_list_data
(
group
,
current_user
,
issues
,
projects
)
expect
(
result
).
to
include
(
expected
)
expect
(
result
).
to
include
(
expected
)
expect
(
result
).
not_to
include
(
:group_epics_path
)
expect
(
result
).
not_to
include
(
:group_epics_path
)
...
...
locale/gitlab.pot
View file @
ec0d8e14
...
@@ -3703,6 +3703,9 @@ msgstr ""
...
@@ -3703,6 +3703,9 @@ msgstr ""
msgid "An error occurred while loading merge requests."
msgid "An error occurred while loading merge requests."
msgstr ""
msgstr ""
msgid "An error occurred while loading projects."
msgstr ""
msgid "An error occurred while loading the Needs tab."
msgid "An error occurred while loading the Needs tab."
msgstr ""
msgstr ""
...
@@ -22660,6 +22663,9 @@ msgstr ""
...
@@ -22660,6 +22663,9 @@ msgstr ""
msgid "New issue"
msgid "New issue"
msgstr ""
msgstr ""
msgid "New issue in %{project}"
msgstr ""
msgid "New issue title"
msgid "New issue title"
msgstr ""
msgstr ""
...
@@ -30496,6 +30502,9 @@ msgstr ""
...
@@ -30496,6 +30502,9 @@ msgstr ""
msgid "Select project to choose zone"
msgid "Select project to choose zone"
msgstr ""
msgstr ""
msgid "Select project to create issue"
msgstr ""
msgid "Select projects"
msgid "Select projects"
msgstr ""
msgstr ""
...
...
spec/frontend/issues_list/components/issues_list_app_spec.js
View file @
ec0d8e14
...
@@ -24,6 +24,7 @@ import IssuableByEmail from '~/issuable/components/issuable_by_email.vue';
...
@@ -24,6 +24,7 @@ import IssuableByEmail from '~/issuable/components/issuable_by_email.vue';
import
IssuableList
from
'
~/issuable_list/components/issuable_list_root.vue
'
;
import
IssuableList
from
'
~/issuable_list/components/issuable_list_root.vue
'
;
import
{
IssuableListTabs
,
IssuableStates
}
from
'
~/issuable_list/constants
'
;
import
{
IssuableListTabs
,
IssuableStates
}
from
'
~/issuable_list/constants
'
;
import
IssuesListApp
from
'
~/issues_list/components/issues_list_app.vue
'
;
import
IssuesListApp
from
'
~/issues_list/components/issues_list_app.vue
'
;
import
NewIssueDropdown
from
'
~/issues_list/components/new_issue_dropdown.vue
'
;
import
{
import
{
CREATED_DESC
,
CREATED_DESC
,
DUE_DATE_OVERDUE
,
DUE_DATE_OVERDUE
,
...
@@ -65,6 +66,7 @@ describe('IssuesListApp component', () => {
...
@@ -65,6 +66,7 @@ describe('IssuesListApp component', () => {
exportCsvPath
:
'
export/csv/path
'
,
exportCsvPath
:
'
export/csv/path
'
,
fullPath
:
'
path/to/project
'
,
fullPath
:
'
path/to/project
'
,
hasAnyIssues
:
true
,
hasAnyIssues
:
true
,
hasAnyProjects
:
true
,
hasBlockedIssuesFeature
:
true
,
hasBlockedIssuesFeature
:
true
,
hasIssueWeightsFeature
:
true
,
hasIssueWeightsFeature
:
true
,
hasIterationsFeature
:
true
,
hasIterationsFeature
:
true
,
...
@@ -93,6 +95,7 @@ describe('IssuesListApp component', () => {
...
@@ -93,6 +95,7 @@ describe('IssuesListApp component', () => {
const
findGlEmptyState
=
()
=>
wrapper
.
findComponent
(
GlEmptyState
);
const
findGlEmptyState
=
()
=>
wrapper
.
findComponent
(
GlEmptyState
);
const
findGlLink
=
()
=>
wrapper
.
findComponent
(
GlLink
);
const
findGlLink
=
()
=>
wrapper
.
findComponent
(
GlLink
);
const
findIssuableList
=
()
=>
wrapper
.
findComponent
(
IssuableList
);
const
findIssuableList
=
()
=>
wrapper
.
findComponent
(
IssuableList
);
const
findNewIssueDropdown
=
()
=>
wrapper
.
findComponent
(
NewIssueDropdown
);
const
mountComponent
=
({
const
mountComponent
=
({
provide
=
{},
provide
=
{},
...
@@ -190,10 +193,7 @@ describe('IssuesListApp component', () => {
...
@@ -190,10 +193,7 @@ describe('IssuesListApp component', () => {
beforeEach
(()
=>
{
beforeEach
(()
=>
{
setWindowLocation
(
search
);
setWindowLocation
(
search
);
wrapper
=
mountComponent
({
wrapper
=
mountComponent
({
provide
:
{
isSignedIn
:
true
},
mountFn
:
mount
});
provide
:
{
isSignedIn
:
true
},
mountFn
:
mount
,
});
jest
.
runOnlyPendingTimers
();
jest
.
runOnlyPendingTimers
();
});
});
...
@@ -208,7 +208,7 @@ describe('IssuesListApp component', () => {
...
@@ -208,7 +208,7 @@ describe('IssuesListApp component', () => {
describe
(
'
when user is not signed in
'
,
()
=>
{
describe
(
'
when user is not signed in
'
,
()
=>
{
it
(
'
does not render
'
,
()
=>
{
it
(
'
does not render
'
,
()
=>
{
wrapper
=
mountComponent
({
provide
:
{
isSignedIn
:
false
}
});
wrapper
=
mountComponent
({
provide
:
{
isSignedIn
:
false
}
,
mountFn
:
mount
});
expect
(
findCsvImportExportButtons
().
exists
()).
toBe
(
false
);
expect
(
findCsvImportExportButtons
().
exists
()).
toBe
(
false
);
});
});
...
@@ -216,7 +216,7 @@ describe('IssuesListApp component', () => {
...
@@ -216,7 +216,7 @@ describe('IssuesListApp component', () => {
describe
(
'
when in a group context
'
,
()
=>
{
describe
(
'
when in a group context
'
,
()
=>
{
it
(
'
does not render
'
,
()
=>
{
it
(
'
does not render
'
,
()
=>
{
wrapper
=
mountComponent
({
provide
:
{
isProject
:
false
}
});
wrapper
=
mountComponent
({
provide
:
{
isProject
:
false
}
,
mountFn
:
mount
});
expect
(
findCsvImportExportButtons
().
exists
()).
toBe
(
false
);
expect
(
findCsvImportExportButtons
().
exists
()).
toBe
(
false
);
});
});
...
@@ -231,7 +231,7 @@ describe('IssuesListApp component', () => {
...
@@ -231,7 +231,7 @@ describe('IssuesListApp component', () => {
});
});
it
(
'
does not render when user does not have permissions
'
,
()
=>
{
it
(
'
does not render when user does not have permissions
'
,
()
=>
{
wrapper
=
mountComponent
({
provide
:
{
canBulkUpdate
:
false
}
});
wrapper
=
mountComponent
({
provide
:
{
canBulkUpdate
:
false
}
,
mountFn
:
mount
});
expect
(
findGlButtons
().
filter
((
button
)
=>
button
.
text
()
===
'
Edit issues
'
)).
toHaveLength
(
0
);
expect
(
findGlButtons
().
filter
((
button
)
=>
button
.
text
()
===
'
Edit issues
'
)).
toHaveLength
(
0
);
});
});
...
@@ -258,11 +258,25 @@ describe('IssuesListApp component', () => {
...
@@ -258,11 +258,25 @@ describe('IssuesListApp component', () => {
});
});
it
(
'
does not render when user does not have permissions
'
,
()
=>
{
it
(
'
does not render when user does not have permissions
'
,
()
=>
{
wrapper
=
mountComponent
({
provide
:
{
showNewIssueLink
:
false
}
});
wrapper
=
mountComponent
({
provide
:
{
showNewIssueLink
:
false
}
,
mountFn
:
mount
});
expect
(
findGlButtons
().
filter
((
button
)
=>
button
.
text
()
===
'
New issue
'
)).
toHaveLength
(
0
);
expect
(
findGlButtons
().
filter
((
button
)
=>
button
.
text
()
===
'
New issue
'
)).
toHaveLength
(
0
);
});
});
});
});
describe
(
'
new issue split dropdown
'
,
()
=>
{
it
(
'
does not render in a project context
'
,
()
=>
{
wrapper
=
mountComponent
({
provide
:
{
isProject
:
true
},
mountFn
:
mount
});
expect
(
findNewIssueDropdown
().
exists
()).
toBe
(
false
);
});
it
(
'
renders in a group context
'
,
()
=>
{
wrapper
=
mountComponent
({
provide
:
{
isProject
:
false
},
mountFn
:
mount
});
expect
(
findNewIssueDropdown
().
exists
()).
toBe
(
true
);
});
});
});
});
describe
(
'
initial url params
'
,
()
=>
{
describe
(
'
initial url params
'
,
()
=>
{
...
...
spec/frontend/issues_list/components/new_issue_dropdown_spec.js
0 → 100644
View file @
ec0d8e14
import
{
GlDropdown
,
GlDropdownItem
,
GlSearchBoxByType
}
from
'
@gitlab/ui
'
;
import
{
createLocalVue
,
mount
,
shallowMount
}
from
'
@vue/test-utils
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
createMockApollo
from
'
helpers/mock_apollo_helper
'
;
import
NewIssueDropdown
from
'
~/issues_list/components/new_issue_dropdown.vue
'
;
import
searchProjectsQuery
from
'
~/issues_list/queries/search_projects.query.graphql
'
;
import
{
DASH_SCOPE
,
joinPaths
}
from
'
~/lib/utils/url_utility
'
;
import
{
emptySearchProjectsQueryResponse
,
project1
,
project2
,
searchProjectsQueryResponse
,
}
from
'
../mock_data
'
;
describe
(
'
NewIssueDropdown component
'
,
()
=>
{
let
wrapper
;
const
localVue
=
createLocalVue
();
localVue
.
use
(
VueApollo
);
const
mountComponent
=
({
search
=
''
,
queryResponse
=
searchProjectsQueryResponse
,
mountFn
=
shallowMount
,
}
=
{})
=>
{
const
requestHandlers
=
[[
searchProjectsQuery
,
jest
.
fn
().
mockResolvedValue
(
queryResponse
)]];
const
apolloProvider
=
createMockApollo
(
requestHandlers
);
return
mountFn
(
NewIssueDropdown
,
{
localVue
,
apolloProvider
,
provide
:
{
fullPath
:
'
mushroom-kingdom
'
,
},
data
()
{
return
{
search
};
},
});
};
const
findDropdown
=
()
=>
wrapper
.
findComponent
(
GlDropdown
);
const
findInput
=
()
=>
wrapper
.
findComponent
(
GlSearchBoxByType
);
const
showDropdown
=
async
()
=>
{
findDropdown
().
vm
.
$emit
(
'
shown
'
);
await
wrapper
.
vm
.
$apollo
.
queries
.
projects
.
refetch
();
jest
.
runOnlyPendingTimers
();
};
afterEach
(()
=>
{
wrapper
.
destroy
();
});
it
(
'
renders a split dropdown
'
,
()
=>
{
wrapper
=
mountComponent
();
expect
(
findDropdown
().
props
(
'
split
'
)).
toBe
(
true
);
});
it
(
'
renders a label for the dropdown toggle button
'
,
()
=>
{
wrapper
=
mountComponent
();
expect
(
findDropdown
().
attributes
(
'
toggle-text
'
)).
toBe
(
NewIssueDropdown
.
i18n
.
toggleButtonLabel
);
});
it
(
'
focuses on input when dropdown is shown
'
,
async
()
=>
{
wrapper
=
mountComponent
({
mountFn
:
mount
});
const
inputSpy
=
jest
.
spyOn
(
findInput
().
vm
,
'
focusInput
'
);
await
showDropdown
();
expect
(
inputSpy
).
toHaveBeenCalledTimes
(
1
);
});
it
(
'
renders expected dropdown items
'
,
async
()
=>
{
wrapper
=
mountComponent
({
mountFn
:
mount
});
await
showDropdown
();
const
listItems
=
wrapper
.
findAll
(
'
li
'
);
expect
(
listItems
.
at
(
0
).
text
()).
toBe
(
project1
.
nameWithNamespace
);
expect
(
listItems
.
at
(
1
).
text
()).
toBe
(
project2
.
nameWithNamespace
);
});
it
(
'
renders `No matches found` when there are no matches
'
,
async
()
=>
{
wrapper
=
mountComponent
({
search
:
'
no matches
'
,
queryResponse
:
emptySearchProjectsQueryResponse
,
mountFn
:
mount
,
});
await
showDropdown
();
expect
(
wrapper
.
find
(
'
li
'
).
text
()).
toBe
(
NewIssueDropdown
.
i18n
.
noMatchesFound
);
});
describe
(
'
when no project is selected
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
mountComponent
();
});
it
(
'
dropdown button is not a link
'
,
()
=>
{
expect
(
findDropdown
().
attributes
(
'
split-href
'
)).
toBeUndefined
();
});
it
(
'
displays default text on the dropdown button
'
,
()
=>
{
expect
(
findDropdown
().
props
(
'
text
'
)).
toBe
(
NewIssueDropdown
.
i18n
.
defaultDropdownText
);
});
});
describe
(
'
when a project is selected
'
,
()
=>
{
beforeEach
(
async
()
=>
{
wrapper
=
mountComponent
({
mountFn
:
mount
});
await
showDropdown
();
wrapper
.
findComponent
(
GlDropdownItem
).
vm
.
$emit
(
'
click
'
,
project1
);
});
it
(
'
dropdown button is a link
'
,
()
=>
{
const
href
=
joinPaths
(
project1
.
webUrl
,
DASH_SCOPE
,
'
issues/new
'
);
expect
(
findDropdown
().
attributes
(
'
split-href
'
)).
toBe
(
href
);
});
it
(
'
displays project name on the dropdown button
'
,
()
=>
{
expect
(
findDropdown
().
props
(
'
text
'
)).
toBe
(
`New issue in
${
project1
.
name
}
`
);
});
});
});
spec/frontend/issues_list/mock_data.js
View file @
ec0d8e14
...
@@ -221,3 +221,37 @@ export const urlParamsWithSpecialValues = {
...
@@ -221,3 +221,37 @@ export const urlParamsWithSpecialValues = {
epic_id
:
'
None
'
,
epic_id
:
'
None
'
,
weight
:
'
None
'
,
weight
:
'
None
'
,
};
};
export
const
project1
=
{
id
:
'
gid://gitlab/Group/26
'
,
name
:
'
Super Mario Project
'
,
nameWithNamespace
:
'
Mushroom Kingdom / Super Mario Project
'
,
webUrl
:
'
https://127.0.0.1:3000/mushroom-kingdom/super-mario-project
'
,
};
export
const
project2
=
{
id
:
'
gid://gitlab/Group/59
'
,
name
:
'
Mario Kart Project
'
,
nameWithNamespace
:
'
Mushroom Kingdom / Mario Kart Project
'
,
webUrl
:
'
https://127.0.0.1:3000/mushroom-kingdom/mario-kart-project
'
,
};
export
const
searchProjectsQueryResponse
=
{
data
:
{
group
:
{
projects
:
{
nodes
:
[
project1
,
project2
],
},
},
},
};
export
const
emptySearchProjectsQueryResponse
=
{
data
:
{
group
:
{
projects
:
{
nodes
:
[],
},
},
},
};
spec/helpers/issues_helper_spec.rb
View file @
ec0d8e14
...
@@ -354,6 +354,7 @@ RSpec.describe IssuesHelper do
...
@@ -354,6 +354,7 @@ RSpec.describe IssuesHelper do
let
(
:group
)
{
create
(
:group
)
}
let
(
:group
)
{
create
(
:group
)
}
let
(
:current_user
)
{
double
.
as_null_object
}
let
(
:current_user
)
{
double
.
as_null_object
}
let
(
:issues
)
{
[]
}
let
(
:issues
)
{
[]
}
let
(
:projects
)
{
[]
}
it
'returns expected result'
do
it
'returns expected result'
do
allow
(
helper
).
to
receive
(
:current_user
).
and_return
(
current_user
)
allow
(
helper
).
to
receive
(
:current_user
).
and_return
(
current_user
)
...
@@ -367,13 +368,14 @@ RSpec.describe IssuesHelper do
...
@@ -367,13 +368,14 @@ RSpec.describe IssuesHelper do
empty_state_svg_path:
'#'
,
empty_state_svg_path:
'#'
,
full_path:
group
.
full_path
,
full_path:
group
.
full_path
,
has_any_issues:
issues
.
to_a
.
any?
.
to_s
,
has_any_issues:
issues
.
to_a
.
any?
.
to_s
,
has_any_projects:
any_projects?
(
projects
).
to_s
,
is_signed_in:
current_user
.
present?
.
to_s
,
is_signed_in:
current_user
.
present?
.
to_s
,
jira_integration_path:
help_page_url
(
'integration/jira/issues'
,
anchor:
'view-jira-issues'
),
jira_integration_path:
help_page_url
(
'integration/jira/issues'
,
anchor:
'view-jira-issues'
),
rss_path:
'#'
,
rss_path:
'#'
,
sign_in_path:
new_user_session_path
sign_in_path:
new_user_session_path
}
}
expect
(
helper
.
group_issues_list_data
(
group
,
current_user
,
issues
)).
to
include
(
expected
)
expect
(
helper
.
group_issues_list_data
(
group
,
current_user
,
issues
,
projects
)).
to
include
(
expected
)
end
end
end
end
...
...
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